一、顺序表和链表的区别和联系:
这两个结构各有优劣势,很难说谁好谁坏,严格来说他们两个相辅相成。
Ⅰ、顺序表:
优点:
①因为其物理结构连续,支持随机访问(用下标访问);
②CPU高速缓存命中率更高(相比链表);
高速缓存是为了解决CPU速率和内存访问速率差距过大问题,所以读取数据之前会将内存中的数据提前加载到缓冲区中。当分别遍历链表或顺序表时,CPU在读取数据时,先看这个数据在不在缓存区中,在就直接访问,不在就先加载到缓存中(首先在高速缓存中查找所需数据判断是否命中,如果命中,立即返回缓存中的副本给CPU;如果不命中,从存储器层次结构中较低层次中取),假设不命中,则一次加载20byte到缓存(具体多大看硬件体系),因为顺序表物理结构是连续的,所以加载的时候可以加载一段连续的数据,增加其命中率。
缺点:
①头部/中部插入/删除数据效率低(时间复杂度O(N));
②空间不够需要增容,增容代价稍高(开辟新空间,拷贝数据,释放旧空间);并且为避免频繁增容,一般我们都是按一定的倍数增长,用不完则存在一定的浪费。
Ⅱ、链表(双向带头循环链表):
优点:
①任意位置插入删除效率高(时间复杂度O(1));
②按需申请释放空间,不存在空间的浪费。
缺点:
①不支持随机访问,意味着一些排序、二分查找等在这种结构上不适用。
②有可能会造成大量内存碎片化。
③CPU高速缓存命中率低。
二、双向带头循环链表实现
结构:
typedef int LTDataType;
typedef struct ListNode
{
LTDataType data;
struct ListNode* prev;
struct ListNode* next;
}ListNode;
创建头结点:(头节点不存储数据)
// 创建返回链表的头结点.
ListNode* ListInit()
{
ListNode* tmp = (ListNode*)malloc(sizeof(ListNode));
if (tmp == NULL)
{
printf("malloc fail\n");
}
ListNode* phead = tmp;
phead->next = phead;
phead->prev = phead;
return phead;
}
打印:
// 双向链表打印
void ListPrint(ListNode* pHead)
{
assert(pHead);
ListNode* cur = pHead->next;
while (cur != pHead)
{
printf("%d->", cur->data);
cur = cur->next;
}
printf("\n");
}
创建新节点、尾插尾删:
//创建新节点
ListNode* BuyListNode(LTDataType x)
{
ListNode* tmp = (ListNode*)malloc(sizeof(ListNode));
if (tmp == NULL)
{
printf("malloc fail\n");
}
ListNode* newnode = tmp;
newnode->data = x;
newnode->next = NULL;
newnode->prev = NULL;
return newnode;
}
// 双向链表尾插
void ListPushBack(ListNode* pHead, LTDataType x)
{
assert(pHead);
ListNode* tail = pHead->prev;
ListNode* newnode = BuyListNode(x);
tail->next = newnode;
newnode->prev = tail;
newnode->next = pHead;
pHead->prev = newnode;
}
// 双向链表尾删
void ListPopBack(ListNode* pHead)
{
assert(pHead);
assert(pHead->prev != pHead);//避免链表只有头结点
ListNode* tail = pHead->prev->prev;
free(tail->next);
tail->next = pHead;
pHead->prev = tail;
}
头插头删:
// 双向链表头插
void ListPushFront(ListNode* pHead, LTDataType x)
{
assert(pHead);
ListNode* newnode = BuyListNode(x);
newnode->next = pHead->next;
newnode->prev = pHead;
pHead->next = newnode;
}
// 双向链表头删
void ListPopFront(ListNode* pHead)
{
assert(pHead && pHead->next != pHead);
ListNode* next = pHead->next->next;
free(pHead->next);
pHead->next = next;
next->prev = pHead;
}
查找:
// 双向链表查找
ListNode* ListFind(ListNode* pHead, LTDataType x)
{
assert(pHead && pHead->next != pHead);
ListNode* cur = pHead->next;
while (cur != pHead)
{
if (cur->data == x)
return cur;
cur = cur->next;
}
return NULL;
}
任意位置插入删除:
// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x)
{
assert(pos);
ListNode* newnode = BuyListNode(x);
ListNode* prev = pos->prev;
newnode->prev = prev;
prev->next = newnode;
newnode->next = pos;
pos->prev = newnode;
}
// 双向链表删除pos位置的节点
void ListErase(ListNode* pos)
{
assert(pos);
ListNode* next = pos->next;
ListNode* prev = pos->prev;
next->prev = prev;
prev->next = next;
free(pos);
pos = NULL;
}
可以用任意位置的insert/erase复用尾插头插/尾删头删:
// 双向链表尾插
void ListPushBack(ListNode* pHead, LTDataType x)
{
assert(pHead);
ListInsert(pHead, x);
}
// 双向链表尾删
void ListPopBack(ListNode* pHead)
{
assert(pHead);
assert(pHead->prev != pHead);//避免链表只有头结点
ListErase(pHead->prev);
}
// 双向链表头插
void ListPushFront(ListNode* pHead, LTDataType x)
{
assert(pHead);
ListInsert(pHead->next, x);
}
// 双向链表头删
void ListPopFront(ListNode* pHead)
{
assert(pHead && pHead->next != pHead);
ListErase(pHead->next);
}
所以如何快速实现一个链表的增删查改,可以先写insert/erase,其他大部分可以复用。