顺序表:

顺序表一般表现为数组,使用一组地址连续的存储单元依次存储数据元素

(1)长度固定,必须在分配内存之前确定数组的长度。

(2)存储空间连续,即允许元素的随机访问。

(3)存储密度大,内存中存储的全部是数据元素。

(4)要访问特定元素,可以使用索引访问,时间复杂度为O(1).

(5)要想在顺序表中插入或删除一个元素,都涉及到之后所有元素的移动,因此时间复杂度为O(n).

wKioL1bvqhrAum6HAAAHDhu9dXI739.png

顺序表最主要的问题就是要求长度是固定的,可以使用倍增-复制的办法来支持动态扩容,将顺序表变成可变长度的。

具体做法是初始情况使用一个初始容量(可以指定)的数组,当元素个数超过数组的长度时,就重新申请一个长度为原先二倍的数组,并将旧的数据复制过去,这样就可以有新的空间来存放元素了。这样,列表看起来就是可变长度的。

一个简单的实现如下所示,初始的容量为 4

#include <string.h>
 
struct sqlist {
int *items, size, capacity;
sqlist() :size(0), capacity(4) 
{
// initial capacity = 4
items = new int[capacity];
}
void doubleCapacity() 
{
capacity *= 2;
int* newItems = new int[capacity];
memcpy(newItems, items, sizeof(int)*size);
delete[] items;
items = newItems;
}
void add(int value) 
{
if (size >= capacity) 
{
doubleCapacity();
}
items[size++] = value;
}
};


这个办法不可避免的会浪费一些内存,因为数组的容量总是倍增的。而且每次扩容的时候,都需要将旧的数据全部复制一份,会影响效率。不过实际上使用顺序表比链表的效率高

 

链表:

链表中的每个节点都保存有指向下一个节点的指针,所有节点串成一条链。根据指针的不同,还有单链表、双链表和循环链表的区分

 

 

 

wKiom1bvqaegxZKAAAAY4Ot5Ges352.png

 

单链表是只包含指向下一个节点的指针,只能单向遍历。

双链表即包含指向下一个节点的指针,也包含指向前一个节点的指针,因此可以双向遍历。

循环单链表则是将尾节点与首节点链接起来,形成了一个环状结构,在某些情况下会非常有用。

由于链表是使用指针将节点连起来,因此无需使用连续的空间,它具有以下特点:

(1)长度不固定,可以任意增删。

(2)存储空间不连续,数据元素之间使用指针相连,每个数据元素只能访问周围的一个元素(根据单链表还是双链表有所不同)。

(3)存储密度小,因为每个数据元素,都需要额外存储一个指向下一元素的指针(双链表则需要两个指针)。

(4)要访问特定元素,只能从链表头开始,遍历到该元素,时间复杂度为O(n) 

(5)在特定的数据元素之后插入或删除元素,不涉及到其他元素的移动,因此时间复杂度为O(1)

(6)双链表还允许在特定的数据元素之前插入或删除元素

用单链表实现扩容:

#include <stdio.h>
#include <time.h>
 
struct node {
int value;
node *next;
};
struct llist {
node *head;
void add(int value) {
node *newNode = new node();
newNode->value = value;
newNode->next = head;
head = newNode;
}
};
 
int main() {
int size = 100000;
sqlist list1;
llist list2;
long start = clock();
for (int i = 0; i < size; i++) {
list1.add(i);
}
long end = clock();
printf("sequence list: %d\n", end - start);
start = clock();
for (int i = 0; i < size; i++) {
list2.add(i);
}
end = clock();
printf("linked list: %d\n", end - start);
return 0;
}


链表的耗时大约是顺序表的 4~8 倍。因为数组只需要很少的几次大块内存分配,而链表则需要很多次小块内存分配,内存分配操作相对是比较慢的,因而大大拖慢了链表的速度。这也是为什么会出现内存池

因此,链表并不像理论分析的那样美好,在实际应用中要受很多条件制约,一般情况下还是安心用顺序表的好。