数据结构 单链表
一、 线性表
1.数据结构中,线性结构习惯称为线性表,线性表是最简单也是最常用的一种数据结构。
2.线性表可以表示为:(A_1,A_2,A_3,…,A_N),其中,A_i(i=1,2,…,n)是线性表的数据元素,也称为线性表的一个结点,同一线性表中的数据元素必定具有相同的特性,即属于同一数据对象。数组、矩阵、向量等都是线性表。大小为0的表为空表。
3.对于除空表外的任何表,我们说A_(i+1)后继A_i并且称A_(i-1)(i<N)前驱A_i(i>1)。表中的第一个元素是A_1,而最后一个元素是A_N。不定义A_1的前驱元,也不定义A_N的后继元。元素A_i在表中的位置为i。
二、链表
1.链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列的结点组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。最后一个结点的指针域为空,用NULL(空指针)表示。用一个单独的指针储存第一个结点的地址(或者头结点的地址),该指针被称为头指针。头指针指向链表中的第一项(或者头结点)。
2.单链表
带头结点的单链表:在单链表的第一个结点之前附设一个头结点,此时头指针指向头结点。
3.单链表的存储结构定义:
#define SIZE 20
typedef struct student{
char num[SIZE]; //学生的学号
char name[SIZE]; //学生的姓名
int grade; //学生的成绩
}Item;
typedef struct node{
Item item; //数据域
struct node * next; //指针域,指向链表中的下一个结构
}Node;
typedef Node * List;
该定义中通过typedef为某一类型自定义名称。用Item作为struct student的别名(这样做更加通用),如果以后需要其他数据形式的链表,可以重新定义Item类型。用Node作为struct node的别名,单链表的结点类型名是Node。用List作为Node *的别名,List作为该类型(Node)的指针名,若有List L;则表示创建了一个链表(L表示指向单链表的头指针,即表示单链表),而不是一个指向结点的指针或一个结构,因为单链表相当于一个指针,我们只关注于他的头指针。
4.单链表的初始化:
void InitList(List * plist) //形参:指向List类型的指针
{
*plist=(Node *)malloc(sizeof(Node)); //建立头结点
(*plist)->next=NULL; //头结点的指针域设置为NULL
}
void InitList(List * plist) //形参:指向List类型的指针
{
*plist=NULL; //链表的头指针指向NULL
}
带头结点的单链表初始化前/后:(初始化后头指针保存头结点的地址)
头指针:使用头指针来标识一个链表,因为我们得知道链表的存储地址才能使用他,而头指针则保存着链表的头结点地址或者链表的首元结点地址,通过头指针可遍历整个链表。(头指针不可缺少)
头结点:在单链表的首元结点前附加一个结点,称为头结点。头结点的数据域可以不设任何信息,也可以记录表长等相关信息。他的存在是为了方便操作(如在首元结点前插入和删除结点等),其可有可无。
首元结点:第一个元素的结点,他是头结点后边的第一个结点。
5.单链表的创建
①:头插法创建单链表:
算法描述:初始为一个空单链表,不断在头结点后面插入新结点,得到非空单链表。
bool CreateFromHead(List * plist,Item item) //形参:1.指向List类型的指针 2.装载数据域的结构变量item
{
Node * pnew; //定义一个结构指针pnew
Node * pend=*plist; //定义一个结构指针pend,并赋予其头结点的地址
pnew=(Node *)malloc(sizeof(Node)); //申请一个结点的空间,并将其地址赋给结构指针pnew
if(pnew==NULL) //若申请失败,则返回false
return false;
Copy(item,pnew); //Copy()用于处理数据域,可以根据程序自定义处理数据域的方式
pnew->next=pend->next; //将头结点指针域的值赋给新结点的指针域
pend->next=pnew; //将头结点的指针域更新为新结点地址
return true; //若操作成功,则返回true
}
②:尾插法创建单链表:
算法描述:初始为一个空单链表,不断在链表最后插入新结点,得到一个非空单链表。
bool CreateFromTail(List * plist,Item item) //形参:1.指向List类型的指针 2.装载数据域的结构变量item
{
Node * pnew; //定义一个结构指针pnew
Node * pend=*plist; //定义一个结构指针pend,并赋予其头结点的地址
pnew=(Node *)malloc(sizeof(Node)); //申请一个结点的空间,并将其地址赋给结构指针pnew
if(pnew==NULL) //若申请失败,则返回false
return false;
Copy(item,pnew); //Copy()用于处理数据域,可以根据程序自定义处理数据域的方式
pnew->next=NULL; //新结点的指针域指向NULL
while(pend->next!=NULL) //遍历链表,找到链表的表尾
pend=pend->next;
pend->next=pnew; //把新结点的地址赋予表尾结点的指针域
return true; //若操作成功,则返回true
}
注意:由图可知:①头插法相对简便,但插入的数据与插入的顺序相反;
②尾插法操作相对复杂,但插入的数据与插入顺序相同。
6.单链表的查找
①:按值查找:
算法描述:按值查找是指在单链表中查找是否有结点值等于待查找值的结点,若有的话,则返回保存该值的结点地址,否则返回NULL。查找过程从单链表的头指针指向的头结点出发,顺着链表逐个将结点的值和给定值作比较。
Node * Locate(List * plist,const char number[]) //形参:1.指向List类型的指针 2.待查找的值(形式自定义)
{
Node * temp; //定义一个结构指针temp
temp=(*plist)->next; //从链表的首元结点开始比较
while(temp!=NULL&&strcmp(temp->item.num,number)!=0) //遍历链表,未找到待查找的值的结点
temp=temp->next; //temp指针后移
return temp; //返回结点地址
}
②:按序号查找:要查找表中第i个结点,则需要从单链表的头指针指向的头结点出发,顺着链域(temp->next)扫描,用指针temp指向当前扫描到的结点, 初值指向头结点,用j做计数器,累计当前扫描过的结点数(初值为0),当j==i时,指针temp所指的结点就是要找的第i个结点。
Node * Get(List * plist,int i) //形参:1.指向List类型的指针 2.链表的第i个结点
{
Node * temp=*plist; //定义一个结构指针temp,并赋予其头结点的地址
int j=0; //定义一个计数器j
if(i<=0) //若i为无效值,则返回NULL
return NULL;
while(temp!=NULL&&j<i) //若当前结点存在且序号不到i
{
temp=temp->next; //temp指针后移,移向下一个结点
j++; //下一个结点的序号
}
return temp; //返回结点地址
}
7.单链表的删除:
算法描述:删除带头结点的单链表中第i个元素,并将删除的元素通过参数带回。
注意:①:删除第i个元素时要查找第i-1个结点。
②:若找不到第i-1个,则参数i不合法
③:若找到第i-1个,但其后继即第i个不存在,则参数i不合法
④:若找到第i-1个,且其后继存在,则作删除操作。
bool DelList(List * plist,int i,Item * item) //形参:1.指向List类型的指针 2.链表的第i个结点 3.指向Item类型的指针,带出被删结点的数据域
{
Node * temp=*plist; //定义一个结构指针temp,并赋予其头结点的地址
Node * dele; //定义一个结构指针dele,用于指向待删结点
int j=0; //定义一个计数器j
if(i<=0) //若i为无效值,则输出提示语句并返回false
{
printf("Remove location illegal!");
return false;
}
while(temp!=NULL&&j<i-1) //遍历链表,找到第i-1个结点, 使temp指向他
{
temp=temp->next;
j++;
}
if(temp==NULL||temp->next==NULL) //若第i-1个或第i个结点不存在,则输出提示语句并返回false
{
printf("Remove location illegal!");
return false;
}
dele=temp->next; //将第i个结点的地址赋予结构指针dele
temp->next=dele->next; //将第i+1个结点(i结点的后继)的地址赋予第i-1个结点(i结点的前驱)的指针域
*item=dele->item; //将待删结点的数据域通过指针带出
free(dele); //释放待删结点所占的内存空间
return true; //若操作成功,则返回true
}
8.单链表的插入:
算法描述:在单链表第i个位置插入元素。
①:先在单链表中找到第i-1个结点,并将其地址赋予结构指针temp。
②:再建立新结点并将其地址赋予结构指针pnew,其数据域的值为item_。
③:修改第i-1个结点的指针使其指向pnew,然后使pnew结点的指针域指向原第i个结点。
bool InsList(List * plist,int i,Item item_) //形参:1.指向List类型的指针 2.链表的第i个结点 3.Item类型的变量item,用于为新结点的数据域赋值
{
Node * temp=*plist; //定义一个结构指针temp,并赋予其头结点的地址
Node * pnew; //定义一个结构指针pnew,用于储存新结点的地址
int j=0; //定义一个计数器j
if(i<=0) //若i为无效值,则输出提示语句并返回false
{
printf("Insert position illegally!");
return false;
}
while(temp!=NULL&&j<i-1) //遍历链表,找到第i-1个结点, 使temp指向他
{
temp=temp->next;
j++;
}
if(temp==NULL) //若第i-1个不存在,则输出提示语句并返回false
{
printf("Insert position illegally!");
return false;
}
pnew=(Node *)malloc(sizeof(Node)); //申请一个结点的空间,并将其地址赋给结构指针pnew
pnew->item=item_; //为新结点的数据域赋值
pnew->next=temp->next; //将原第i个结点的地址赋予新结点的指针域
temp->next=pnew; //将新结点的地址赋予第i-1个结点的指针域
return true;
}
9.单链表的长度:
算法描述:求带头结点单链表的长度。从单链表的第一个结点开始计数,直到链表尾。
注意:若temp从头结点开始,temp为空时,计数器比实际长度大1。
int ListLength(const List * plist) //形参:指向List类型的指针,由于该函数不修改单链表,使用const修饰符对指针所指向的内容进行保护
{
Node * temp=(*plist)->next; //定义一个结构指针temp,并赋予首元结点的地址
int i=0; //定义一个计数器i
while(temp!=NULL) //遍历单链表
{
i++;
temp=temp->next;
}
return i; //返回单链表的长度
}
10.单链表的输出
算法描述:通过遍历单链表,输出每个结点的数据域。(输出形式由数据域的类型决定)
void output(const List * plist) //形参:指向List类型的指针,由于该函数不修改单链表,使用const修饰符对指针所指向的内容进行保护
{
Node * temp; //定义一个结构指针temp
temp=(*plist)->next; //赋予temp首元结点的地址
while(temp!=NULL) //遍历单链表
{
printf("%-10s %-10s %-10d\n",temp->item.num,temp->item.name,temp->item.grade); //输出形式由数据域的类型决定
temp=temp->next;
}
}
11.单链表的清除:
算法描述:释放了为链表分配的所有内存,单链表设置为空表。
void EmptyTheList(List * plist) //形参:指向List类型的指针
{
Node * psave; //定义一个结构指针psave
psave=(*plist)->next; //将首元结点的地址赋予结构指针psave
while((*plist)->next!=NULL) //头结点的指针域不为NULL(空)做循环
{
psave=psave->next; //更新结构指针psave的指向,第一次:赋予第二结点地址,第二次:赋予第三结点地址...以此类推
free((*plist)->next); //释放结点所占的内存空间
(*plist)->next=psave; //更新头结点的指针域,第一次:指向第二节点,第二次:指向第三节点...以此类推
}
}
12.总结:
①:链表的大小取决于可用内存量。若尝试为新项分配空间失败,则说明链表已满。
②:函数的参数*plist是一个指向链表的指针,而不是一个链表。
③:遍历链表时,不直接使用头指针。需要重新创建一个新指针。因为如果使用头指针会改变他的指向。程序就找不到链表的开始处了。
④:带头结点的单链表查找(两种)操作、插入操作、删除操作的时间复杂度都是O(n)。这里的前提是想要查找、插入、删除的结点地址未知。因为在做这些操作时,都需要遍历单链表,即做循环。while循环体中的语句频度与被查、插、删的结点在表中的位置有关,若1<=i<=n,则频度为i-1,否则频度为n,即时间复杂度为O(n)。
13.样例:
#include<stdio.h>
#include<stdlib.h> //包含malloc()函数
#include<string.h> //包含strcmp()等字符串函数
#include<stdbool.h> //包含bool类型的头文件
#define SIZE 20
typedef struct student{
char num[SIZE]; //学生的学号
char name[SIZE]; //学生的姓名
int grade; //学生的成绩
}Item;
typedef struct node{
Item item; //数据域
struct node * next; //指针域,指向链表中的下一个结构
}Node;
typedef Node * List;
int ListLength(const List * plist);
void output(List * plist);
void InitList(List * plist);
void EmptyTheList(List * plist);
void Copy(Item item,Node * pnode);
bool CreateFromTail(List * plist,Item item);
bool InsList(List * plist,int i,Item item_);
bool DelList(List * plist,int i,Item * item);
Node * Get(List * plist,int i);
Node * Locate(List * plist,const char number[]);
int main()
{
List student; //创建一个头指针
Item temp; //用于暂时保存数据域
Item temp3; //用于插入结点的数据域
Node * temp1; //用于按值查找
Node * temp2; //用于按序号查找
int i,j,k; //i用于按序号查找 j用于删除操作 k用于插入操作
char number[SIZE]; //用于按值查找,保存学号
InitList(&student);
puts("Enter first student num:");
while(gets(temp.num)&&temp.num[0]!='#') //循环输入,直到学号为#
{
puts("Enter student name:");
gets(temp.name);
puts("Enter student grade:");
scanf("%d",&temp.grade);
if(CreateFromTail(&student,temp)==false) //尾插法创建单链表
{
printf("Application memory error!");
break;
}
while(getchar()!='\n') //处理scanf()遗留在缓冲区的换行符
continue;
puts("Enter next student num:");
}
printf("Here is the student list:\n");
output(&student);
printf("The length of the list is:%d\n",ListLength(&student));
puts("Please enter student num to find:");
gets(number);
temp1=Locate(&student,number);
printf("Information for students num %s:%s %s %d\n"
,number,temp1->item.num,temp1->item.name,temp1->item.grade);
puts("Please enter a serial number to find:");
scanf("%d",&i);
temp2=Get(&student,i);
printf("Information for the serial number is %d:%s %s %d\n"
,i,temp2->item.num,temp2->item.name,temp2->item.grade);
puts("Please enter the need to delete the node number:");
scanf("%d",&j);
DelList(&student,j,&temp3);
output(&student);
puts("Please enter the want to insert into position:");
scanf("%d",&k);
InsList(&student,k,temp3);
output(&student);
EmptyTheList(&student);
printf("Bye!");
return 0;
}
void InitList(List * plist) //初始化
{
*plist=(Node *)malloc(sizeof(Node));
(*plist)->next=NULL;
}
void Copy(Item item,Node * pnode) //尾插法时处理新结点的数据域
{
pnode->item=item;
}
bool CreateFromTail(List * plist,Item item) //尾插法创建单链表
{
Node * pnew;
Node * pend=*plist;
pnew=(Node *)malloc(sizeof(Node));
if(pnew==NULL)
return false;
Copy(item,pnew);
pnew->next=NULL;
while(pend->next!=NULL)
pend=pend->next;
pend->next=pnew;
return true;
}
void output(List * plist) //单链表输出
{
Node * temp;
temp=(*plist)->next;
while(temp!=NULL)
{
printf("%-10s %-10s %-10d\n",temp->item.num,temp->item.name,temp->item.grade);
temp=temp->next;
}
}
Node * Locate(List * plist,const char number[]) //按值查找
{
Node * temp;
temp=(*plist)->next;
while(temp!=NULL&&strcmp(temp->item.num,number)!=0)
temp=temp->next;
return temp;
}
Node * Get(List * plist,int i) //按序号查找
{
int j=0;
Node * temp;
temp=*plist;
if(i<=0)
return NULL;
while(temp!=NULL&&j<i)
{
temp=temp->next;
j++;
}
return temp;
}
bool DelList(List * plist,int i,Item * item) //单链表删除
{
Node * temp=*plist;
Node * dele;
int j=0;
if(i<=0)
{
printf("Remove location illegal!");
return false;
}
while(temp!=NULL&&j<i-1)
{
temp=temp->next;
j++;
}
if(temp==NULL||temp->next==NULL)
{
printf("Remove location illegal!");
return false;
}
dele=temp->next;
temp->next=dele->next;
*item=dele->item;
free(dele);
return true;
}
int ListLength(const List * plist) //单链表的长度
{
Node * temp=(*plist)->next;
int i=0;
while(temp!=NULL)
{
i++;
temp=temp->next;
}
return i;
}
bool InsList(List * plist,int i,Item item_) //单链表的插入
{
Node * temp=*plist;
Node * pnew;
int j=0;
if(i<=0)
{
printf("Insert position illegally!");
return false;
}
while(temp!=NULL&&j<i-1)
{
temp=temp->next;
j++;
}
if(temp==NULL)
{
printf("Insert position illegally!");
return false;
}
pnew=(Node *)malloc(sizeof(Node));
pnew->item=item_;
pnew->next=temp->next;
temp->next=pnew;
return true;
}
void EmptyTheList(List * plist) //单链表的清空
{
Node * psave;
psave=(*plist)->next;
while((*plist)->next!=NULL)
{
psave=psave->next;
free((*plist)->next);
(*plist)->next=psave;
}
}
输出结果: