何为链表?
链表是一种常见的基础数据结构,结构体指针在这里得到了充分的利用。
链表可以动态的进行存储分配,也就是说,链表是一个功能极为强大的数组,他可以在节点中定义多种数据类型,还可以根据需要随意增添,删除,插入节点。
链表都有一个头指针,一般以head来表示,存放的是一个地址。链表中的节点分为两类,头结点和一般节点,头结点是没有数据域的。链表中每个节点都分为两部分,一个数据域,一个是指针域。
链表就如同车链子一样,head指向第一个元素:第一个元素又指向第二个元素;……,直到最后一个元素,该元素不再指向其它元素,它称为“表尾”,它的地址部分放一个“NULL”(表示“空地址”),链表到此结束。
作为有强大功能的链表,对他的操作有许多,eg:链表的创建,修改,删除,插入,输出,排序,反序,清空链表的元素,求链表的长度等等。
链表的特点
n个节点离散分配
每一个节点之间通过指针相连
每一个节点有一个前驱节点和一个后继节点
首节点没有前驱节点,尾节点没有后继节点
为何要创建链表
malloc在没有连续内存空间的时候分配会失败
解决方案:
不要一次性开辟一块连续的存储空间, 每次少开辟一点
最后利用指针将所有开辟的小的存储空间链接在一起, 组成一个整体
链表的创建
创建链表须知
首节点: 存放第一个有效数据的节点
头节点: 在单链表的第一个结点之前附设一个结点,它没有直接前驱,称之为头结点,头结点的数据域可以不存储任何信息(也可以用来存储链表的节点数),指针域指向第一个节点(首节点)的地址。头结点的作用是使所有链表(包括空表)的头指针非空
头指针: 指向头节点的指针
尾节点: 存放最后一个有效数据的节点
尾指针: 指向尾节点的指针
建立创建链表需要的结构体
typedef struct link{
int data; //数据域
struct link *next; //指针域
} Link;
数据域的内容自己设置,指针域存放下一个节点的地址。
malloc函数
其函数原型如下:
void *malloc(unsigned int size);
这个函数返回的是个void类型指针,所以在使用时应注意强制类型转换成需要的指针类型。
free函数
其函数原型如下:
void free(void *p);
这个函数是用来释放指针p作指向的内存区。
空链表
只有一个头指针和头结点,并且头结点没有下一个结点且没有数据
Link *createLink();
int main()
{
Link *head = createLink();
return 0;
}
Link*createLink()
{
//创建一个头结点
Link *head= (Link *)malloc(sizeof(Link));
//可能分配失败
if(head == NULL) exit(-1);
//下一个节点赋值为空
head -> next = NULL;
return head;
}
单向不循环链表
链表头插法
1.把新结点的下一个结点指向头结点的下一个结点
2.把头结点的下一个结点指向新结点
链表数据打印
void printList(Link * head)
{
Link *cur = head;
while(cur->next != NULL)
{
cur = cur -> next;
printf("%d\n",cur -> data);
}
}
头插法代码
#include <stdio.h>
#include <stdlib.h>
typedef struct link
{
int data;
struct link *next;
} Link;
void printList(Link * head);
Link *createList_TC();
int main()
{
//创建头结点和头指针
Link *head = createList_TC();
//打印数据
printList(head);
return 0;
}
/**
* @brief createList 头插法创建动态链表,先进后出
* @return 返回头指针
*/
Link *createList_TC()
{
//创建头指针和头结点
Link *head = (Link *)malloc(sizeof(Link));
if(head == NULL) exit(-1);
head -> next = NULL;
//用一个变量表示循环条件
//提示用户输入要保存的数据
int num = 0;
printf("输入保存的数据,输入1结束\n");
scanf("%d",&num);
while(1 != num)
{
Link *link= (Link *)malloc(sizeof(Link));
//保存数据
link-> data = num;
//1.把新结点的下一个结点指向头结点的下一个结点
link-> next = head -> next;
//2.把头结点的下一个结点指向新结点
head -> next = link;
printf("输入保存的数据,输入1结束\n");
scanf("%d",&num);
}
return head;
}
链表尾插法
1.定义变量记录最后一个结点
2.让新结点成为上一个结点的下一个结点
3.把新结点作为下一个结点的上一个结点
尾插法代码
#include <stdio.h>
#include <stdlib.h>
typedef struct link
{
int data;
struct link *next;
} Link;
Link *createList_WC();
void printList(Link *head);
int main()
{
Link *head = createList_WC();
printList(head);
return 0;
}
/**
* @brief createList 动态链表尾插法,先进先出
* @return 返回头指针
*/
Link *createList_WC()
{
//1.创建头结点和头指针
Link *head = (Link*)malloc(sizeof(Link));
//分配失败
if(head == NULL) exit(-1);
head->next = NULL;
//定义变量控制循环
int num = 1;
printf("输入保存的数据,输入1结束:\n");
scanf("%d",&num);
//1.定义变量记录最后一个结点
Link *cur = head;//一定要放在最外面,否则会引起值覆盖
while(1 != num)
{
Link *link = (Link*)malloc(sizeof(Link));
link->data = num;
link->next = NULL;
//让新结点的上一个结点的下一个结点指向新结点
cur->next = link;
//把新结点作为下一个结点的上一个结点
cur = link;
printf("输入保存的数据,输入1结束:\n");
scanf("%d",&num);
}
return head;
}
单向循环链表
单向循环链表和普通的链表区别在于尾节点的next指向头节点,使得链表头尾相连,构成循环链表。
一般动态链表创建完成之后再插入节点基本都是使用头插法的方式,没有使用尾插法,这是单向链表的限制,第一没有办法直接取得尾节点的地址(非循环),当然在循环链表中是可以定义一个end节点为尾节点,可以直接取得尾节点地址,这就为非迭代取地址插入链表,直接在链表最后插入节点提供了可能性,当然应为C语言的参数传递问题,在子涵中执行插入时,end节点应为全局变量,从而使得可以变更end节点的地址。
void createEmpty_XH();
void insertLink_WC(int data);
Link *head,*end;//设为全局变量,暂未分配内存空间
int main()
{
createEmpty_XH();//创建循环空链表
int n=0;
for(;n<4;n++)
{
insertLink_WC(n);//在链表尾端插入节点和数据
}
printList(head);
return 0;
}
void printList(Link *head)//节点数保存在head.data中
{
Link *cur = head;
int n=0;
printf("保存了%d组数据\n",head -> data);
for (;n<head->data;n++)//首先判断不是空链表
{
cur = cur -> next;
printf("%d\n",cur -> data);
}
}
/**
* @brief createEmpty 创建循环空链表
* @return 链表的头指针
*/
void createEmpty_XH()
{
//2.创建一个空节点, 并且赋值给头指针
head = (Link *)malloc(sizeof(Link));
if(head == NULL) exit(-1);
end = (Link *)malloc(sizeof(Link));
if(end == NULL) exit(-1);
head->next = end;//首尾相连
end->next=head;
head->data=0;
}
/**
* @brief insertLink_WC插入节点
* @param data 插入节点的数据
* //此时,end要做为全局变量,或者该函数在main中直接实现(不以子函数的形式调用)
*/
void insertLink_WC(int data)
{
Link *link = (Link *)malloc(sizeof(Link)); // 创建一个新的节点
if(link == NULL) exit(-1);
Link *cur=end->next;
cur->data++; //head.data++
end->data = data; // 将数据保存到新节点中
link->next=end->next; //保存end.next的地址到中间节点
cur=link; //保存新节点地址到中间节点
link=end; //end的地址赋值给新节点
end=cur; //中间节点地址赋值给end节点
link->next=end; //新节点之后是end节点
}
链表常用函数封装总(head,end已设置为全局变量)
/**
* @brief createList_TC 动态链表头插法,先进后出
* @return 返回头指针
*/
void createList_TC()
{
//1.创建头指针和头结点
head = (Link *)malloc(sizeof(Link));
if(head == NULL) exit(-1);
head -> next = NULL;
head->data=0;
//1.用一个变量表示循环条件
//2.提示用户输入要保存的数据
int data = 0;
printf("输入想要保存的数据,输入1结束\n");
scanf("%d",&data);
while(1!= data)
{
Link *link= (Link *)malloc(sizeof(Link));
//保存数据
link-> data = data;
//1.把新结点的下一个结点指向头结点的下一个结点
link-> next = head -> next;
//2.把头结点的下一个结点指向新结点
head -> next = link;
head->data++;
printf("输入想要保存的数据,输入1结束\n");
scanf("%d",&data);
}
}
/**
* @brief createList_WC 动态链表尾插法,先进先出
* @return 返回头指针
*/
void createList_WC()
{
//1.创建头结点和头指针
head = (Link*)malloc(sizeof(Link));
//分配失败
if(head == NULL) exit(-1);
head->next = NULL;
head->data=0;
//定义变量控制循环
int data = -1;
printf("请输入你想要保存的数据:\n");
scanf("%d",&data);
//1.定义变量记录最后一个结点
Link *cur = head;//一定要放在最外面,否则会引起值覆盖
while(-1 != data)
{
Link *link = (Link*)malloc(sizeof(Link));
link->data = data;
link->next = NULL;
//2.让新结点的上一个结点的下一个结点指向新结点
cur->next = link;
//3.把新结点作为下一个结点的上一个结点
cur = link;
head->data++;
printf("请输入你想要保存的数据:\n");
scanf("%d",&data);
}
}
/**
* @brief createEmpty 创建循环空链表
* @return 链表的头指针
*/
void createEmpty_XH()
{
//2.创建一个空节点, 并且赋值给头指针
head = (Link *)malloc(sizeof(Link));
if(head == NULL) exit(-1);
end = (Link *)malloc(sizeof(Link));
if(end == NULL) exit(-1);
head->next = end;//首尾相连
end->next=head;
head->data=0;
}
void createEmpty()
{
//创建一个空节点, 并且赋值给头指针
head = (Link *)malloc(sizeof(Link));
if(head == NULL) exit(-1);
head->next = NULL;
head->data=0;
}
/**
* @brief insertLink_TC插入节点
* @param data 插入节点的数据
*/
void insertLink_TC(int data)
{
Link *link = (Link *)malloc(sizeof(Link)); // 1.创建一个新的节点
if(link == NULL) exit(-1);
link->data = data; // 2.将数据保存到新节点中
// 3.进行插入
// 3.1让新节点的下一个节点 等于 头节点的下一个节点
link->next = head->next;
// 3.2让头结点的下一个节点 等于 新节点
head->next = link;
head->data++;
}
/**
* @brief insertLink_WC插入节点
* @param data 插入节点的数据
* //此时,end要做为全局变量,或者该函数在main中直接实现(不以子函数的形式调用)
*/
void insertLink_WC(int data)
{
Link *link = (Link *)malloc(sizeof(Link)); // 创建一个新的节点
if(link == NULL) exit(-1);
Link *cur=end->next;
cur->data++; //head.data++
end->data = data; // 将数据保存到新节点中
link->next=end->next; //保存end.next的地址到中间节点
cur=link; //保存新节点地址到中间节点
link=end; //end的地址赋值给新节点
end=cur; //中间节点地址赋值给end节点
link->next=end; //新节点之后是end节点
}
/**
* @brief insertLink插入节点
* @param front 在第几个节点后插入节点
* @param data 插入节点的数据
*/
int insertLink(int front,int data)
{
if(front<(head->data+1))
{
int count=0;
Link *link,*cur=head;
link = (Link *)malloc(sizeof(Link)); //创建一个新的节点
if(link == NULL) exit(-1);
link->data = data; //将数据保存到新节点中
//进行插入
for(;count<front;count++) cur=cur->next;
link->next = cur->next;
cur->next = link;
head->data++;
return 1;
}
else return 0;
}
/**
* @brief insertData插入数据
* @param front 插入数据的节点,是第几个
* @param data 插入节点的数据
*/
int insertData(int front,int data)
{
if(front<(head->data+1))
{
Link *cur=head;
int count=0;
for(;count<front;count++) cur=cur->next;
cur->data = data; //将数据插入到节点中
return 1;
}
else return 0;
}
/**
* @brief destroyList 销毁链表
* @param head 链表的头指针
*/
void destroyList(Link *head)
{
Link *cur = NULL;
while(head != NULL)
{
cur = head->next;
free(head);
head = cur;
}
}
/**
* @brief findLink 查找指定节点
* @param key 需要查找的key
* @return 符合要求的节点, 如果找不到返回NULL
*/
Link *findLink(int key)
{
Link *cur = head->next; //注意点:头结点不需要查找
int count=0;
while(count<head->data)
{ // 判断当前节点保存的值是否是要查找的值
if(cur->data == key) return cur;
else cur = cur->next;
}
return NULL;
}
/**
* @brief deleteLink 删除指定节点
* @param head 链表的头指针
* @param link 需要删除的节点
*/
void deleteLink(Link *link)
{
Link *cur=head;
//找到需要删除节点的上一个节点
while(cur->next != link) cur = cur->next;
cur->next = link->next;
free(link); // 3.删除对应节点
}
link->next = NULL和link!=NULL的区别:
while(link->next != NULL) 循环结束时,此时link的位置是尾节点的位置,但如果用于输出函数的判断条件,则尾节点的数据不会输出。
while(link!=NULL) 循环结束时, 此时link指向的位置为尾节点的下一个节点,因为没有申请内存空间,所以是一个未知的区域。