目录
1.1双向链表的结点结构
typedef int LTDataType;
typedef struct ListNode
{
struct ListNode* next;
struct ListNode* prev;
LTDataType data;
}ListNode;
使用typedef:如果一开始我们就确定了结构体中的变量类型,后续在项目过程中如果需要对这个变量类型进行调整,那么所需的操作是很繁琐的。故使用typedef,后续若是需要修改,改动typedef就足够了。
1.2双向链表的创建
ListNode* BuyListNode(LTDataType x)
{
ListNode* node = (ListNode*)malloc(sizeof(ListNode));
if (node == NULL)
{
perror("错误原因:");
exit(-1);
}
node->data = x;
node->next = NULL;
node->prev = NULL;
return node;
}
1.3双向链表的初始化
不需要二级指针是因为其带了哨兵位的头结点
ListNode* ListInit()
{
ListNode* phead = BuyListNode(0);
phead->next = phead;
phead->prev = phead;
return phead;
}
1.4双向链表的尾插
尾插先找尾,而phead的prev为尾
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;
}
如果只插入一个结点是否需要改正上述代码?不需要
1.5双向链表的打印
注意循环终止的条件
void ListPrint(ListNode* phead)
{
ListNode* cur = phead->next;
while (cur != phead)
{
printf("%d", cur->data);
cur = cur->next;
}
printf("\n");
}
1.6双向链表的头插法
void ListPushFront(ListNode* phead, LTDataType x)
{
assert(phead);
ListNode* first = phead->next;
ListNode* newnode = BuyListNode(x);
phead->next = newnode;
newnode->prev = phead;
newnode->next = first;
first->prev = newnode;
}
用头插法只插入一个结点是否可行?可行
1.7双向链表的尾删法
void ListNodePopBack(ListNode* phead)
{
assert(phead);
ListNode* tail = phead->prev;
ListNode* tailPrev = tail->prev;
free(tail);
tailPrev->next = phead;
phead->prev = tailPrev;
}
只有一个结点时(非头结点)是否可用上述代码?进入free循环时头结点会被删除
改正:加一步判断 assert(phead->next != phead);
1.8双向链表的头删法
void ListNodePopFront(ListNode* phead)
{
assert(phead);
assert(phead->next != phead);
ListNode* first = phead->next;
ListNode* second = first->next;
free(first);
phead->next = second;
second->prev = phead;
}
1.9双向链表查找值
ListNode* ListFind(ListNode* phead, LTDataType x)
{
assert(phead);
ListNode* cur = phead->next;
while (cur != phead)
{
if (cur->data == x)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
1.10双向链表的插入(前插)
void ListInsert(ListNode* pos, LTDataType x)
{
assert(pos);
ListNode* prev = pos->prev;
ListNode* newnode = BuyListNode(x);
prev->next = newnode;
newnode->prev = prev;
newnode->next = pos;
pos->prev = newnode;
}
pos为头和尾代码能否运行?若x = 4
相当于尾插(在phead前面插入一个结点)
由此,尾插法的代码可改为
ListInsert(phead,x);
头插法可改为
ListInsert(phead->next,x);
1.11双向链表的删除
void ListErase(ListNode* pos )
{
assert(pos);
ListNode*prev = pos->prev;
ListNode*next = pos->next;
prev->next = next;
next->prev = prev;
free(pos);
}
由此,尾删除法可改为
ListErase(phead->prev);
头删法可改为
ListErase(phead->next)
1.12判断双向链表是否为空(空返回1,非空返回0)
int ListEmpty(ListNode* phead)
{
assert(phead);
return phead->next == phead ? 1 : 0;
}
1.13求双链表长度
int ListSize(ListNode* phead)
{
assert(phead);
int size = 0;
ListNode* cur = phead->next;
while (cur != phead)
{
++size;
cur = cur->next;
}
return size;
}
1.14销毁双链表
void ListDestory(ListNode* phead)
{
assert(phead);
ListNode* cur = phead->next;
while (cur != phead)
{
ListNode* next = cur->next;//提前保存它的下一个
//ListErase(cur);
free(cur);
cur = next;
}
free(phead);
//phead = NULL;
}
使用函数的时候注意将野指针置(或者用二级指针,因为传一级指针时函数内部的置空不起作用)
ListDestory(plist);
plist = NULL;
补充:顺序表和链表的区别
不同点 | 顺序表 | 链表 |
存储空间上 | 物理上连续 | 逻辑上连续,但物理上不连续 |
随机访问 | 支持O(1) | 不支持:O(N) |
任意位置插入或者删除元素 | 可能需要搬移元素,效率低,O(N) | 只需修改指针指向 |
插入 | 动态顺序表,空间不够时需要扩容 | 没有容量的概念 |
应用场景 | 元素高效存储+频繁访问 | 任意位置插入和删除频繁 |
缓存利用率 | 高 | 低 |
优点 | 1.按下标去进行随机访问 2、cpu高速缓存命中率比较高 | 1.按需申请内存,需要存一个数据就申请一块内存 2.任意位置插入和删除数据效率高,O(1) |
缺点 | 1.空间不够需要增容(一定程序的性能消耗)可能存在一定的空间浪费 2、头部或者中间插入删除数据需要挪动数据,O(N) | 不支持下标的随机访问 |