1、链表的概念及结构
1.1链表的概念
链表是一种物理存储结构上非连续、非顺序的存储结构,但在逻辑上确是连续、顺序的,而数据元素的逻辑顺序是通过链表中的指针链接次序实现的。
1.2链表的结构
如下图:
逻辑上的链表,pList是指向头个元素的指针
物理上的链表
有人可能会有疑问,不是说链表只是在逻辑结构上是连续的,在物理存储结构上是不连续的,那为什么上图中一个个结点明明是挨在一起的,那么它在物理存储结构上肯定是连续的呀,其实不然,上图是为了方便大家理解,才用线条连接了结点,实际上在内存中,每个结点可能会隔得很远,仔细观察每个结点上面的红色文字,那就是这个结点的地址,而蓝色文字是下一个结点的地址,很明显能看到这两个结点并不是相邻的,因此也验证了顺序表在逻辑结构上确实是连续的,但在物理存储结构上确实是不连续的。
2、链表的实现
2.1 定义结点
介绍一下单链表的英文名——single linked list,我们简写成SL(区别于顺序表的SeqList或者SQL)。
注意:next指针的类型是SListNode*,千万不要写成SLTDataType*,因为next指针是结构体指针,是用来指向下一个结点的
typedef int SLTDataType;
typedef struct SListNode
{SLTDataType data;struct SListNode* next;
}SLTNode;
2.2链表的功能
链表要实现那些功能呢?其实这些功能我们都很熟悉,数据结构无非是对数据进行管理,要实现数据的增删查改,因此链表的基本功能也都是围绕着数据的增删查改展开。
注意:链表是不需要初始化的
//打印单链表
void SLTPrint(SLTNode* phead);
//创建一个结点
SLTNode* BuyLTNode(SLTDataType x);
//销毁单链表
void SLTDestroy(SLTNode** pphead);
//头插
void SLPushFront(SLTNode** pphead, SLTDataType x);
//尾插
void SLPushBack(SLTNode** pphead, SLTDataType x);
//头删
void SLPopFront(SLTNode** pphead);
//尾删
void SLPopBack(SLTNode** pphead);
// 单链表查找
SLTNode* STFind(SLTNode* phead, SLTDataType x);// 在pos之前插入
void SLInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);
//在pos之后插入
void SLInsertAfter(SLTNode* pos, SLTDataType x);// 删除pos位置的值
void SLErase(SLTNode** pphead, SLTNode* pos);// 删除pos位置后面的值
void SLEraseAfter(SLTNode* pos);// 单链表结点修改
void SLTModify(SLTNode* phead, SLTNode* pos, SLTDataType x);
2.3链表功能实现
2.3.1打印单链表
注意:链表和顺序不同的是,顺序表传过来的指针是肯定不会为空的,而链表传过来的指针是可能为空的,比如说当链表中没有元素时,头指针所指向的就是NULL,如果在第一行写上断言就会有问题。
当cur指向空的时候就可以停止打印了。
void SLTPrint(SLTNode* phead)
{SLTNode* cur=phead;while(cur!=NULL){printf("%d->",cur->data);cur=cur->next;}printf("NULL\n");}
2.3.2 创建一个新结点
后面我们要在单链表中进行头插和尾插,此时插入的不再是像顺序表一样简单的SLDataType数据了,而是一个结点,这个结点是包括SLTDataType数据以及SLTDataType*的指针,因此,为了方便和减少代码的重复度,我们另写一个函数用来专门创建新结点。
SLTNode* BuyLTNode(SLTDataType x)
{SLTNode * newnode=(SLTNode*)malloc(sizeof(SLTNode));if(newnode==NULL){perror("malloc fail");return NULL;}newnode->data=x;newnode->next=NULL;//初始化return newnode;
}
2.3.3 单链表尾插
注意:在创建结点时,已经让 结点.next=NULL,所以不需要在插入完结点后,再让新结点的next指针为NULL。
有人可能会有疑问,为什么之前打印链表的时候不用断言指针,而在尾插时就要断言指针,以及为什么函数的形参是二级指针,而不使用一级指针。
因为,尾插分为两种情况(下面有写),当链表为空时,头指针phead指向NULL,尾插相当于头插,此时要改变phead的指向,让phead指向这个新结点,此时就需要二级指针来改变一级指针的值(如果我们用一级指针做形参,形参的改变不会影响实参,那么一级指针phead就不会被改变)。
至于这个什么时候要断言指针,什么时候不用断言指针:一级指针也就是phead,当链表为空的时候,phead就是为NULL,而二级指针永远指向phead,phead的地址是永远存在的,那么pphead就一定不可能为空,所以需要断言pphead。
//尾插
//第一个尾插需要二级指针,接下来就不用二级指针
//第一次是改变结构的指针plist
//第二次是改变结构体尾结点
void SLPushBack(SLTNode** pphead, SLTDataType x)
{SLTNode *newnode= BuyLTNode(x);//是否为空链表if(*pphead==NULL)*pphead=newnode;//改变结构的指针plistelse{SLTNode *tail=*pphead;while(tail->next!=NULL)//找到链表最后一位,当tail->为空时,就把新开辟的链表节点接上去tail=tail->next;tail->next=newnode;//改变结构体尾结点//就是把newnode存放的新结点地址给tail->next,然后在存放在之前最后一个结点的struct SListNode* next;中,这样next指向的地址不是NULL,而是新加的结点的位置//不能用tail=newnode,因为tail和newnode是局部变量,当这函数结束后就释放了}
}
2.2.4 单链表尾删
要想删除链表中的元素,就必须保证原链表就有元素,因此要断言assert(*pphead)。
尾删需要分情况去讨论
//尾删
void SLPopBack(SLTNode** pphead)
{//当没有结点(空链表)//暴力检查assert(*pphead);//温柔检查
// if(*pphead==NULL)
// return;//当只有一个结点时if((*pphead)->next==NULL){free(*pphead);*pphead=NULL;}else{SLTNode *prev = NULL;//用来指向倒数第二个结点SLTNode *tail = *pphead;//用来释放最后一个指针空间//找尾while (tail->next) {prev = tail;tail = tail->next;}free(tail);//把最后一个指针空间释放掉prev->next = NULL;//把最后一个的前一个结点指针设为空//如果直接tail=NULL的话,倒数第二个结点就指向一个NULL,就成为了一个野指针//while(tail->next->next)//找到倒数第二个// tail=tail->next;//free(tail->next);//把倒数第二个结点指向的结构体释放掉//tail->next=NULL;//把倒数第二个结点置为空}
}
2.2.5 单链表头删
头删没什么好说的,记住要断言*pphead,保证链表内容不为空。
//头删
void SLPopFront(SLTNode** pphead)
{//空链表assert(*pphead);// //一个结点
// if((*pphead)->next==NULL)
// {
// free(*pphead);
// *pphead=NULL;
// }
// //多个结点
// else
// {
// SLTNode *del=*pphead;
// //*pphead=del->nest;
// *pphead=(*pphead)->next;
// free(del);
// }SLTNode* del = *pphead;*pphead = (*pphead)->next;free(del);}
2.2.6 单链表头插
现在是不是觉得头插很简单了啊!
//头插
void SLPushFront(SLTNode** pphead, SLTDataType x)
{SLTNode *newnode= BuyLTNode(x);newnode->next= *pphead;*pphead=newnode;//都需要二级指针
}
2.2.7 查找某个结点
这个函数返回值不再是void,而是SLTNode*,把找到的结点的地址返回去,这个函数一般会跟结点修改之类的函数一起使用。
SLTNode* STFind(SLTNode* phead, SLTDataType x)
{SLTNode *cur=phead;while (cur){if(cur->data==x){return cur;}cur=cur->next;}return NULL;
}
2.2.8 删除某个节点
// 删除pos位置的值
void SLErase(SLTNode** pphead, SLTNode* pos)
{assert(pphead);assert(pos);//如果只有一个结点if(pos==*pphead)SLPopFront(pphead);else{SLTNode *p1=*pphead;while(p1->next!=pos)p1=p1->next;p1->next=pos->next;free(pos);}
}
注意:
- pos也要断言,pos可不能为空呀!
- cur->next也要断言,因为当cur->next为NULL时,说明整个链表的结点都排查完了,最后还是没有找到地址为pos的结点,证明pos传值有误。
2.2.9单链表结点修改
// 单链表结点修改
void SLTModify(SLTNode* phead, SLTNode* pos, SLTDataType x)
{SLTNode* cur=phead;while(cur!=pos){cur=cur->next;assert(cur);}pos->data=x;
}
2.2.10 单链表在pos位前插入结点
// 在pos之前插入
void SLInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{assert(pphead);assert(pos);//如果只有一个结点if(*pphead==pos)SLPushFront(pphead,x);else{SLTNode *p1=*pphead;while(p1->next!=pos){p1=p1->next;}SLTNode *newnode= BuyLTNode(x);p1->next=newnode;newnode->next=pos;}
}
2.2.11单链表在pos位后插入结点
//在pos之后插入
void SLInsertAfter(SLTNode* pos, SLTDataType x)
{assert(pos);SLTNode *newnode= BuyLTNode(x);newnode->next=pos->next;pos->next=newnode;
}
2.2.12销毁链表
销毁链表这一块,咱可不敢直接free(phead),因为链表在物理结构上是不连续存储的,销毁链表必须要一个结点一个结点去销毁!!!!最后不要忘记把phead置为NULL。
//单链表销毁
void SListDesTroy(SLTNode** pphead)
{assert(pphead && *pphead );SLTNode *pcur=*pphead;while(pcur){SLTNode *next=pcur->next;free(pcur);pcur=next;}*pphead=NULL;}
3、总代码
3.1 SList.h
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>typedef int SLTDataType;
typedef struct SListNode
{SLTDataType data;struct SListNode* next;
}SLTNode;void SLTPrint(SLTNode* phead);
//头插
void SLPushFront(SLTNode** pphead, SLTDataType x);
//尾插
void SLPushBack(SLTNode** pphead, SLTDataType x);
//头删
void SLPopFront(SLTNode** pphead);
//尾删
void SLPopBack(SLTNode** pphead);
// 单链表查找
SLTNode* STFind(SLTNode* phead, SLTDataType x);// 在pos之前插入
void SLInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);
//在pos之后插入
void SLInsertAfter(SLTNode* pos, SLTDataType x);// 删除pos位置的值
void SLErase(SLTNode** pphead, SLTNode* pos);// 删除pos位置后面的值
void SLEraseAfter(SLTNode* pos);//单链表销毁
void SListDesTroy(SLTNode** pphead);// 单链表结点修改
void SLTModify(SLTNode* phead, SLTNode* pos, SLTDataType x);
3.2 SList.c
#include "SList.h"void SLTPrint(SLTNode* phead)
{SLTNode* cur=phead;while(cur!=NULL){printf("%d->",cur->data);cur=cur->next;}printf("NULL\n");}//创建一个新的动态内存
SLTNode* BuyLTNode(SLTDataType x)
{SLTNode * newnode=(SLTNode*)malloc(sizeof(SLTNode));if(newnode==NULL){perror("malloc fail");return NULL;}newnode->data=x;newnode->next=NULL;//初始化return newnode;
}//头插
void SLPushFront(SLTNode** pphead, SLTDataType x)
{SLTNode *newnode= BuyLTNode(x);newnode->next= *pphead;*pphead=newnode;//都需要二级指针
}//尾插
//第一个尾插需要二级指针,接下来就不用二级指针
//第一次是改变结构的指针plist
//第二次是改变结构体尾结点
void SLPushBack(SLTNode** pphead, SLTDataType x)
{SLTNode *newnode= BuyLTNode(x);//是否为空链表if(*pphead==NULL)*pphead=newnode;//改变结构的指针plistelse{SLTNode *tail=*pphead;while(tail->next!=NULL)//找到链表最后一位,当tail->为空时,就把新开辟的链表节点接上去tail=tail->next;tail->next=newnode;//改变结构体尾结点//就是把newnode存放的新结点地址给tail->next,然后在存放在之前最后一个结点的struct SListNode* next;中,这样next指向的地址不是NULL,而是新加的结点的位置//不能用tail=newnode,因为tail和newnode是局部变量,当这函数结束后就释放了}
}//头删
void SLPopFront(SLTNode** pphead)
{//空链表assert(*pphead);// //一个结点
// if((*pphead)->next==NULL)
// {
// free(*pphead);
// *pphead=NULL;
// }
// //多个结点
// else
// {
// SLTNode *del=*pphead;
// //*pphead=del->nest;
// *pphead=(*pphead)->next;
// free(del);
// }SLTNode* del = *pphead;*pphead = (*pphead)->next;free(del);}//尾删
void SLPopBack(SLTNode** pphead)
{//当没有结点(空链表)//暴力检查assert(*pphead);//温柔检查
// if(*pphead==NULL)
// return;//当只有一个结点时if((*pphead)->next==NULL){free(*pphead);*pphead=NULL;}else{SLTNode *prev = NULL;//用来指向倒数第二个结点SLTNode *tail = *pphead;//用来释放最后一个指针空间//找尾while (tail->next) {prev = tail;tail = tail->next;}free(tail);//把最后一个指针空间释放掉prev->next = NULL;//把最后一个的前一个结点指针设为空//如果直接tail=NULL的话,倒数第二个结点就指向一个NULL,就成为了一个野指针//while(tail->next->next)//找到倒数第二个// tail=tail->next;//free(tail->next);//把倒数第二个结点指向的结构体释放掉//tail->next=NULL;//把倒数第二个结点置为空}
}// 单链表查找
SLTNode* STFind(SLTNode* phead, SLTDataType x)
{SLTNode *cur=phead;while (cur){if(cur->data==x){return cur;}cur=cur->next;}return NULL;
}// 在pos之前插入
void SLInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{assert(pphead);assert(pos);//如果只有一个结点if(*pphead==pos)SLPushFront(pphead,x);else{SLTNode *p1=*pphead;while(p1->next!=pos){p1=p1->next;}SLTNode *newnode= BuyLTNode(x);p1->next=newnode;newnode->next=pos;}
}//在pos之后插入
void SLInsertAfter(SLTNode* pos, SLTDataType x)
{assert(pos);SLTNode *newnode= BuyLTNode(x);newnode->next=pos->next;pos->next=newnode;
}// 删除pos位置的值
void SLErase(SLTNode** pphead, SLTNode* pos)
{assert(pphead);assert(pos);//如果只有一个结点if(pos==*pphead)SLPopFront(pphead);else{SLTNode *p1=*pphead;while(p1->next!=pos)p1=p1->next;p1->next=pos->next;free(pos);}
}// 删除pos位置后面的值
void SLEraseAfter(SLTNode* pos)
{assert(pos);assert(pos->next);SLTNode *newnode=pos->next;pos->next=newnode->next;free(newnode);
}//单链表销毁
void SListDesTroy(SLTNode** pphead)
{assert(pphead && *pphead );SLTNode *pcur=*pphead;while(pcur){SLTNode *next=pcur->next;free(pcur);pcur=next;}*pphead=NULL;}
// 单链表结点修改
void SLTModify(SLTNode* phead, SLTNode* pos, SLTDataType x)
{SLTNode* cur=phead;while(cur!=pos){cur=cur->next;assert(cur);}pos->data=x;
}
3.3 text.c
测试函数可以根据大家的想法进行测试,下面是我的测试函数以及输出情况
#include"SList.h"void TestSList1()
{SLTNode* plist = NULL;SLPushFront(&plist, 1);SLPushFront(&plist, 2);SLPushFront(&plist, 3);SLPushFront(&plist, 4);SLTPrint(plist);
}void TestSList2()
{SLTNode *plist=NULL;SLPushFront(&plist, 1);SLPushFront(&plist, 2);SLPushFront(&plist, 3);SLPushFront(&plist, 4);SLPushBack(&plist, 5);SLPushBack(&plist, 6);SLPushBack(&plist, 7);SLPushBack(&plist, 8);SLTPrint(plist);SLTNode *pos= STFind(plist,3);if(pos){SLInsert(&plist,pos,30);}SLTPrint(plist);pos= STFind(plist,6);if(pos){SLInsertAfter(plist,88);}SLTPrint(plist);pos= STFind(plist,7);if(pos){SLErase(&plist,pos);}SLTPrint(plist);pos= STFind(plist,1);if(pos){SLEraseAfter(pos);}SLTPrint(plist);SListDesTroy(&plist);
}void Swapi(int* p1, int* p2)
{int tmp = *p1;*p1 = *p2;*p2 = tmp;
}void Swappi(int** pp1, int** pp2)
{int* tmp = *pp1;*pp1 = *pp2;*pp2 = tmp;
}int main()
{
// TestSList1();TestSList2();
// int a = 0, b = 1;
// Swapi(&a, &b);
//
// int* px = &a, * py = &b;
// Swappi(&px, &py);return 0;
}