单链表的基本操作大全之C语言实现
单链表的基本操作大全之C语言实现
1. 单链表的定义
链表是通过一组任意的存储单元来存储线性表中的数据元素,这些存储单元可以是连续的也可以是不连续的。为了建立起数据元素之间的关系,对于每个数据元素除了存放数据元素自身的信息外,还必须有包含的指示该元素直接后继元素存储位置的信息,这两部分信息组成一个结点,即每个结点都有至少包括两个域,一个域存储数据元素信息,称为数据域,另一个域存储直接后继的地址,称为指针域。
typedefstruct node{
int data; //数据域
struct node *next; //指针域
}NODE;
当n个元素的线性表通过每个结点的指针域连接成了一条“链子”,我们形象的称之为链表。
2. 建立单链表
建立单链表有两种方法,一种是头插法,一种是尾插法。
在主函数中调用函数时,使用一个NODE *类型的指针接收函数的返回值。
2.1初始化一个链表结点
每次malloc的结点都要初始化,因此写成一个函数。
//初始化一个节点
NODE *initnode(NODE *pnode, int data)
{
pnode= (NODE *)malloc(sizeof(NODE));
pnode->data=data;//初始化数据域
pnode->next =NULL;//初始化指针域为NULL
return pnode;
}
2.2头插法建立链表
在链表的头部插入结点建立单链表,称为“头插法”。
//创建一个新节点(头插法)
NODE *createlink_byhead(NODE *phead, int data)
{
NODE *pnode=initnode(pnode, data);//初始化一个结点
NODE *ptmp=phead;
if(NULL==phead){ //链表为空,直接返回初始化的结点
return pnode;
}else{
phead=pnode; //将新申请的结点插在phead后面,也就是整个链表的最前面
pnode->next =ptmp;
}
return phead;
}
因为每次新结点都会插在链表的头部,则数据读入的顺序和线性表中的逻辑顺序正好相反。
2.3尾插法建立链表
在链表的尾部插入结点建立单链表,简称”尾插法“。
//创建一个链表节点(尾插法)
NODE *createlink_bytail(NODE *phead, int data)
{
NODE *pnode=initnode(pnode, data);//初始化结点
NODE *ptmp=phead;
if(NULL==phead){//当链表为空,直接返回初始化的结点
return pnode;
}else{
while(ptmp->next !=NULL){//找到最后一个结点,插在尾部ptmp=ptmp->next;
}
ptmp->next =pnode;
}
return phead;
}
尾插法读入的数据与线性表中的逻辑顺序相同3. 链表的长度
//求链表长度
int linklen(NODE *phead)
{
intlen = 0;//计数器
NODE *ptmp = phead;
while(ptmp != NULL){//遍历链表,计数器加1
len++;
ptmp = ptmp->next;
}
return len;
}
这个算法的时间复杂度为O(n)
4. 按值查找操作
//按值查找
NODE *searchnode(NODE *phead, int key)
{
NODE *ptmp=phead;
if(NULL==phead){ //链表为空,直接返回
return NULL;
}
while(ptmp->data!= key &&ptmp->next !=NULL){//没找到key &&链表没找完
ptmp=ptmp->next; //指针移动
}
if(ptmp->data== key) //找到key,返回该结点地址
return ptmp;
if(ptmp->next ==NULL)//没找到,返回空
return NULL;
}
这个算法的时间复杂度为O(n)
5.插入操作
插入操作也分为两种,一种是插在目标值得前面,简称“前插法”,另一种是插在目标值的后面,简称“后插法”。
5.1前插法
//向前插入
NODE *insertnode_bypre(NODE *phead, int data, int key)
{
NODE *pnode=initnode(pnode, data);//初始化插入的结点
NODE *ptmp=phead;
if(NULL==phead){//链表为空,直接返回初始化的结点
return pnode;
}elseif(phead->data== key){//处理第一个结点是否为目标结点
phead=pnode;
pnode->next =ptmp;
}else{
while(ptmp->next !=NULL&&ptmp->next->data!= key){//循环遍历,没找完&&没找到ptmp=ptmp->next;
}
if(NULL==ptmp->next){//没找到的情况
printf("insert key NOT FOUND\n");
}else{//把新节点插入目标结点的前面
ptmp->next->data== key;
pnode->next =ptmp->next;
ptmp->next =pnode;
}
}
return phead;
}
前插法单独处理了第一个结点,是因为前插法会改变phead的值,而且前插法查找目标结点必须知道目标节点的前驱结点的next值,所以从第二个结点开始处理。
5.2后插法
//向后插入
NODE *insertnode_byback(NODE *phead, int data, int key)
{
NODE *pnode=initnode(pnode, data);//初始化插入的结点
NODE *ptmp=searchnode(phead, key);//查找到目标结点
if(NULL==ptmp){//处理链表为空,或没找到
printf("link is empty or not found key\n");
return phead;
}
if(NULL==ptmp->next){//如果key为最后一个结点
ptmp->next =pnode;
}else{ //将新节点插入在目标结点的后面
pnode->next =ptmp->next;
ptmp->next =pnode;
}
return phead;
}
后插法要单独处理尾结点的情况,因为插入操作不相同。
前插法和后插法的时间复杂度都为O(n)
6.删除操作
//删除节点
NODE *delnode(NODE *phead, int key)
{
NODE *ptmp=phead;
NODE *tmp=NULL;
if(NULL==phead){//处理链表为空的情况
printf("link is empty, del fail\n");
return NULL;
}elseif(phead->data== key){//单独处理第一个结点
phead=phead->next;
free(ptmp);
ptmp=NULL;
}else{
while(ptmp->next !=NULL&&ptmp->next->data!= key){ //没找完&&没找到
ptmp=ptmp->next;
}
if(NULL==ptmp->next){ //没找到
printf("del key NOT FOUND\n");
return phead;
}
if(ptmp->next->data== key){ //找到目标结点删除之
tmp=ptmp->next;
ptmp->next =tmp->next;
free(tmp);
tmp=NULL;
}
}
return phead;
}
单独处理第一个结点还是因为要知道要删除目标结点的前驱结点
删除操作算法时间复杂度为O(n)
7.链表的倒置
链表的倒置算法思路:一次取出原链表中的每一个结点,将其作为第一个结点插入新链表中。
//链表的倒置
NODE *reverselink(NODE *phead)
{
NODE *ptmp=NULL;//创建一个新链
NODE *tmp=NULL;
if(NULL==phead){//处理链表为空
printf("link is empty\n");
return NULL;
}else{
while(phead!=NULL){//将旧链上的结点链到新链上
tmp=phead;
phead=phead->next;
tmp->next =ptmp;
ptmp=tmp;
}
}
return ptmp;
}
8.链表的排序(冒泡排序)//链表排序(冒泡)
NODE *linksort(NODE *phead)
{
NODE *p =NULL; //i
NODE *q =NULL; //j
NODE *r =NULL;
NODE tmp;
for(p =phead; p; p = p->next){
for(q = p->next; q; q = q->next){
if(p->data> q->data){
tmp=*p;//整个结点交换
*p =*q;
*q =tmp;
r = p->next;//将指针换回去
p->next = q->next;
q->next = r;
}
}
}
return phead;
}
冒泡排序的时间复杂度为O(n2)