数据结构 单链表(C语言)

数据结构 单链表

一、 线性表
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;
     }
}

输出结果:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值