1.1链表的概念
定义:
链表是一种物理存储上非连续,数据元素的逻辑顺序通过链表中的指针链接次序,实现的一种线性存储结构。
特点:
链表由一系列节点组成,节点在运行时动态生成 (malloc),每个节点包括两个部分:
一个是存储数据元素的数据域
另一个是存储下一个节点地址的指针域
如图:
1.2链表的概述
链表作为 C 语言中一种基础的数据结构,在平时写程序的时候用的并不多,但在操作系统里面使用的非常多。不管是RTOS还是Linux等使用非常广泛,所以必须要搞懂链表,链表分为单向链表和双向链表,单向链表很少用,使用最多的还是双向链表。单向链表懂了双向链表自然就会了。
1.3单向不带头不循环的链表的初始化
参数的传入:涉及改变链表的操作统统用指针传链表(其中特别注意的是需要改变传入的头结点时需要传入二级指针)不然函数调用完成之后,为传入的链表分配的的内存会自动释放,链表不会有任何改变。创建头结点,为头结点分配内存。
令头节点的指针为空指针(指针不初始化容易出现很多问题)
PS:这里为什么要动态分配内存呢?
因为线性表的顺序存储结构用数组实现,而数组占用的是一整块内存,数组元素分布很集中,需要提前预定数组的长度。而链表是一种动态结构,它所占用的大小和位置是不需要提前分配的,可以根据自身的需求即时生成。
第二步:创建第二个节点,将其放在第一个节点的后面(第一的节点的指针域保存第二个节点的地址)
第三步:再次创建节点,找到原本链表中的最后一个节点,接着讲最后一个节点的指针域保存新节点的地址,以此内推。
1.4单链表的一些操作
1.4.1链表的遍历
1.输出第一个节点的数据域,输出完毕后,让指针保存后一个节点的地址
2.输出移动地址对应的节点的数据域,输出完毕后,指针继续后移
3.以此类推,直到节点的指针域为NULL。
1.4.2链表的查找
// 单链表查找
SListNode* SListFind(SListNode* plist, SLTDateType x) {
assert(plist);
SListNode* cur = plist;
while (cur)
{
if (cur->data == x)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
1.4.3链表的头插头删,尾插尾删
// 单链表的头插
void SListPushFront(SListNode** pplist, SLTDateType x)
{
if (*pplist == NULL)
{
SListNode* newnode = BuySListNode(x);
*pplist = newnode;
}
else
{
SListNode *newnode = BuySListNode(x);
newnode->next=*pplist;
*pplist = newnode;
}
}
// 单链表头删
void SListPopFront(SListNode** pplist)
{
assert(*pplist);
if ((*pplist)->next == NULL)//对结构体指针的地址传值时访问该结构体的成员先得对结构体地址解引用
{
SListNode* cur = *pplist;
//直接free(*pplist),会使得pplist被置为随机值
free(cur);
*pplist = NULL;
}
else
{
SListNode *cur = *pplist;
*pplist = (*pplist)->next;//左右两边的星号都不要漏。
free(cur);
}
}
// 单链表尾插
void SListPushBack(SListNode** pplist, SLTDateType x)
{
if (*pplist == NULL)
{
SListNode* newnode = BuySListNode(x);
*pplist = newnode;
}
else
{
SListNode* tail = *pplist;
SListNode* newnode = BuySListNode(x);
while (tail->next)
{
tail = tail->next;
}
tail->next = newnode;
}
}
// 单链表的尾删
void SListPopBack(SListNode** pplist) {
assert(*pplist);
SListNode* tail=*pplist,*prev=*pplist;
if (tail->next == NULL)
{
free(tail);
*pplist = NULL;
}
else
{
while (tail->next)
{
prev = tail;
tail = tail->next;
}
prev->next = NULL;
free(tail);
tail = NULL;
}
}
1.4.4单链表的插入
// 单链表在pos位置之后插入x
// 分析思考为什么不在pos位置之前插入?因为找pos之前的位置需要O(n)的时间复杂度
void SListInsertAfter(SListNode* pos, SLTDateType x) {
assert(pos);
SListNode* newnode = BuySListNode(x);
newnode->next = pos->next;
pos->next = newnode;
}
// 单链表删除pos位置之后的值
void SListEraseAfter(SListNode* pos) {
assert(pos&&pos->next);
SListNode* next = pos->next;
pos->next = next->next;
free(next);
next = NULL;
}
// 单链表在pos位置之后插入x
// 分析思考为什么不在pos位置之前插入?
因为找pos之前的位置需要O(n)的时间复杂度进行查找到pos的前一个元素
1.4.5链表节点的删除
如果链表为空,不需要删除
如果删除的是第一个结点,则需要将保存链表首地址的指针保存第一个结点的下一个结点的 地址
如果删除的是中间结点,则找到中间结点的前一个结点,让前一个结点的指针域保存这个结 点的后一个结点的地址即可
// 单链表删除pos位置之后的值
void SListEraseAfter(SListNode* pos) {
assert(pos&&pos->next);
SListNode* next = pos->next;
pos->next = next->next;
free(next);
next = NULL;
}
1.4.6单链表的销毁
重新定义一个指针q,保存p指向节点的地址,然后p后移保存下一个节点的地址,然后释放q对应的节点,以此类推,直到p为NULL为止。
// 单链表的销毁
void SListDestroy(SListNode* plist) {
while (plist)
{
SListNode* next = plist;
free(plist);
plist = next;
}
}
1.5双向带头不循环的链表
虽然听起来复杂,但其实只是结构复杂,但是遍历还是增删查改插入和删除等都是十分简单的
#include"List.h"
ListNode* BuyListNode(int x)
{
ListNode* new = (ListNode*)malloc(sizeof(ListNode));
new->data = x;
new->next = NULL;
new->prev = NULL;
return new;
}
// 创建返回链表的头结点.
ListNode* ListCreate(void)
{
ListNode* newhead = (ListNode*)malloc(sizeof(ListNode));
newhead->next = newhead;
newhead->prev = newhead;
return newhead;
}
// 双向链表销毁
void ListDestory(ListNode* pHead)
{
assert(pHead);
ListNode* cur = pHead;
while (cur)
{
ListNode* next = cur->next;
free(cur);
cur = next;
}
}
// 双向链表打印
void ListPrint(ListNode* pHead)
{
assert(pHead);
ListNode* cur = pHead->next;
while (cur != pHead)
{
printf("%d->", cur->data);
cur = cur->next;
}
printf("NULL\n");
}
// 双向链表尾插
void ListPushBack(ListNode* pHead, LTDataType x)
{
assert(pHead);
ListNode* tail = pHead->prev;
ListNode* new = BuyListNode(x);
new->next = tail->next;
tail->next->prev = new;
new->prev = tail;
tail->next = new;
}
// 双向链表尾删
//需要注意此处如果链表初始化后不断删除会使得pHead指针指向的地方不确定,如果后续用户未重新创造双向链表的哨兵位而继续插入数据,会导致非法访问。
//或者加个条件判断判断是否只有一个哨兵位,是的话就不再进行删除。
void ListPopBack(ListNode* pHead)
{
assert(pHead);
ListNode* tail = pHead->prev;
tail->prev->next= tail->next;//左边修改的是指针域,右边是具体的链表的位置。
tail->next->prev = tail->prev;
free(tail);
//tail = NULL;
}
// 双向链表头插
void ListPushFront(ListNode* pHead, LTDataType x)
{
ListInsert(pHead->next, x);
}
// 双向链表头删
void ListPopFront(ListNode* pHead)
{
assert(pHead);
ListNode* cur = pHead->next;
if (cur != pHead)
{
pHead->next = cur->next;
cur->next->prev = pHead;
free(cur);
}
cur = NULL;
}
// 双向链表查找
ListNode* ListFind(ListNode* pHead, LTDataType x) {
assert(pHead);
ListNode* cur = pHead->next;
while (cur != pHead)
{
if (cur->data == x)
{
return cur;
}
cur = cur->next;
}
if (cur == pHead)
{
return NULL;
}
}
// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x) {
assert(pos);
ListNode *new=BuyListNode(x);
ListNode* prev = pos->prev;
new->next = pos;
pos->prev = new;
new->prev = prev;
prev->next = new;
}
// 双向链表删除pos位置的节点
void ListErase(ListNode* pos) {
ListNode* prev = pos->prev;
prev->next = pos->next;
pos->next->prev = prev;
free(pos);
}
着重注意这段
// 双向链表尾删
//需要注意此处如果链表初始化后不断删除会使得pHead指针指向的地方不确定,如果后续用户未重新创造双向链表的哨兵位而继续插入数据,会导致非法访问。
//或者加个条件判断判断是否只有一个哨兵位,是的话就不再进行删除。
void ListPopBack(ListNode* pHead)
{
assert(pHead);
ListNode* tail = pHead->prev;
tail->prev->next= tail->next;//左边修改的是指针域,右边是具体的链表的位置。
tail->next->prev = tail->prev;
free(tail);
//tail = NULL;
}