2.2 线性表的链式表示和实现(单链表)

一言之善,贵于千金。

                                     ———葛洪                                          

目录

 

一、前言

二、链式表示介绍

三、线性表的单链表存储结构

四、带头结点的单链表

         五、单链表的常用操作(带头结点)

结构体、头文件、宏定义

   1.头插法创建链表                    图示       代码                       2.尾插法创建链表                           图示          代码

   3.查找某一个数据是否在链表中,如果没有,返回false,否则返回该结点的位序                         图示          代码

   4.统计结点个数                       图示       代码                       5.删除第i个结点                              图示          代码

   6.在某个位置之前插入结点      图示       代码                       7.修改第i个结点的数据域的值          8.打印链表

        六、完整代码

        七、总结

  1.链表的优缺点

  2.数组的优缺点

  3.链表操作注意事项


 

一、前言

学习了线性表的顺序表示,我们知道顺序表示可以随机存取元素,意味着逻辑上相邻的两个元素在物理位置上也相连,但是我们可以看到插入和删除元素需要移动大量元素,而且还需要事先分配好一定量的空间。那么有没有一种结构是不要求物理位置必须相邻,而且插入删除也很快速呢?这就是我们要说的链式结构。

二、链式表示介绍

链式存储结构意味着逻辑上相邻的元素在物理位置上不一定也相邻,在删除和插入元素时不需要移动大量元素,同时不支持随机存取,必须找到直接前驱和直接后继才能操作相应的元素。

对于单链表来说,每个元素需要存储自身的值之外,还需要存储下一个元素的地址(直接后继地址),这是与顺序表示的最大区别,这时我们不称做这种存储结构为元素,我们称这种结构为结点,即链表是由有限个结点组成的,和顺序表示的元素说法来进行区分。其中每个结点存储自身信息的区域成为数据域,存储直接后继地址的区域称为指针域。指针域中存储的信息称作指针或链。

由于此链表中的每个结点只包含一个指针域,故又称为线性链表或单链表,整个链表的存取必须从头指针开始进行,头指针指向链表中第一个结点。

我们可以看到,每个结点的指针域内存放的是下一个设施的地址,这些设施虽然逻辑上相连,但并没有在同个路上。 最后一个结点由于后面没有结点需要指向,所以指针域为NULL(空),即什么也不指向。

由上述可见,单链表可由头指针唯一确定,在C语言中可以用结构指针来描述。

三、线性表的单链表存储结构

typedef struct LNode{
	int            data;
	struct LNode * next; //指向结构体类型的指针,表示下一个结点 
}LNode,* LinkList;

/*  最后一句的等同于下面代码,中括号是我自己加上的,只是提示把他们看作是一个整体
 
typedef  {struct  LNode   }  LNode;      // struct  LNode 的别名是 LNode
 
typedef  {struct  LNode * }  LinkList;   // struct  LNode *  的别名是 LinkList

*/

四、带头结点的单链表

什么是头结点? 如字面意思,头结点就是在第一个结点前面增加一个结点,位置在头指针和第一个结点之间。

详细信息可以看这篇博客

https://blog.csdn.net/xiaohaiguang/article/details/105657135

以下是书中说明:

假设L是LinkList型的变量,则L为单链表的头指针,它指向表中第一个结点。若L为“空”(L= NULL),则所表示的线性表为“空”表,其长度n为

“零”。有时,我们在单链表的第一个结点之前附设一个结点,称之为头结点。头结点的数据域可以不存储任何信息,也可存储如线性表

的长度等类的附加信息,头结点的指针域存储指向第一个结点的指针(即第一个元素结点的存储位置)。

带头结点的非空链表与空链表

五、单链表的常用操作(带头结点)

结构体、头文件、宏定义

#include<cstdio>
#include<cstdlib>
#include<malloc.h>
#include<cstring>
#include<conio.h>
//函数结果状态代码 
#define TRUE  1
#define FALSE 0
#define OK    1
#define ERROR 0 

typedef int Status;  //int 的别名,其值是函数结果状态代码 

typedef struct LNode{
	int            data;
	struct LNode * next; //指向结构体类型的指针,表示下一个结点 
}LNode,* LinkList;

1.头插法创建链表

图示

所谓头插法就是插入的元素顺序和实际在链表中的顺序相反,比如  插入 1,2   输出就是  2,1    

头插法建立链表

 

代码

 Status create_headLinked_List(LinkList & L,int n) 
 //逆序输入n个元素的值,建立带表头结点单链线性表L 
 {
 	LinkList head = (LinkList)malloc(sizeof(LNode));//头结点的存储地址 
 	if(NULL == head) return ERROR;                  //分配空间失败 
 	L = head;                                       //头指针L存储头结点P的地址,即头指针指向头结点 
 	L->data = 0;                                    //用头结点统计元素个数,刚开始为0个 
 	L->next = NULL;                                 //此时链表为空 
 	for(int i = 0;i<n;i++)
	 {
 		LinkList List_Node = (LinkList)malloc(sizeof(LNode));
 		scanf("%d",&List_Node->data);
 		L->data++;                               //每插入一个元素,头结点统计链表元素个数加一 
 		List_Node->next = L->next;               //头插法,即链表中的元素顺序与输入相反 
 		L->next = List_Node;
	 } 
	 return OK;
 }

 

2.尾插法创建链表

图示

尾插法创建单链表

代码

Status tail_create_heahLinked_List(LinkList & L,int n) 
 {
 	LinkList head = (LinkList)malloc(sizeof(LNode));//头结点的存储地址 
 	if(NULL == head) return ERROR;                  //分配空间失败 
 	L = head;                                       //头指针L存储头结点P的地址,即头指针指向头结点 
 	L->data = 0;                                    //用头结点统计元素个数,刚开始为0个 
 	L->next = NULL;                                 //此时链表为空
	LinkList temp = L;                               
 	for(int i = 0;i<n;i++)
	 {
 		LinkList List_Node = (LinkList)malloc(sizeof(LNode));
 		scanf("%d",&List_Node->data);
 		L->data++;  
		List_Node->next = NULL;               //每插入一个元素,头结点统计链表元素个数加一 
 		temp->next = List_Node;               //尾插法,即链表中的元素顺序与输入相同 
 		temp = temp->next;
	 } 
	 return OK;
 }

3.查找某一个数据是否在链表中,如果没有,返回false,否则返回该结点的位序。

图示

代码

 Status  getLNodeIndex(LinkList L,int number)
 {
 	int i=1;
 	LinkList  temp =L->next;
 	while(temp)
	 {
 		if(number == temp->data) return i;
 		temp = temp->next;
 		i++;
	 }
	 return FALSE;
 }
 

4.统计结点个数

图示

我们直接利用头结点存储结点的个数,或者遍历统计,这里就不再演示这种方法。

代码

 Status getLength(LinkList L)
 {
 	return L->data;
 } 

5.删除第i个结点

图示

删除第i个结点

代码

Status  deleteLnode(LinkList L ,int i)
 {
 	LinkList temp = L;
 	int j=1;
	while(temp->next && j<i )  //结束循环时,temp指向待删除结点的前一个结点
	{
		temp = temp->next;
		j++;
	}  
	if(NULL == temp->next || j > i)  return ERROR;//循环结束,若最终待删除结点为空,或i为0或负数时,说明位置不合理,直接返回 
	LinkList p = temp->next; //p是待删除结点
	temp->next = p->next;    //将p的下一个结点地址赋值给p的前一个结点 
	free(p);
	L->data--;               
	return OK;
 } 

6.在某个位置之前插入结点

图示

在某个位置之前插入结点

代码

Status  insertLnode(LinkList L ,int i,int value)
{
	LinkList temp = L;
	int j=1;
	LinkList newLnode = (LinkList)malloc(sizeof(LNode));
	while(j<i)
	{
		temp = temp->next;
		j++;
	}
	
	if( j > i || i>L->data + 1 )   return ERROR;//允许在最后一个元素之后插入元素 
	newLnode->data = value;
	newLnode->next = temp->next;
	temp->next = newLnode;
	L->data++;
	return OK;
} 

7.修改第i个结点的数据域的值

Status  updateLnode(LinkList L,int i,int value)
{
	LinkList temp = L;
 	int j=1;
	while(temp->next && j<i )
	{
		temp = temp->next;
		j++;
	}  
	if(NULL == temp->next || j > i)  return ERROR;
        //循环结束,若最终待修改结点为空,或i为0或负数时,说明位置不合理,直接返回 
        temp->next->data = value; 
	return OK;
} 

8.打印链表

 void headlinked_List_Traverse(LinkList  L)
 {
 	
    printf("该链表的元素个数为:%d \n",L->data);
	LinkList p = L->next;    
 	while(p)
	{    
 	        printf(" %d ",p->data);	
		p = p->next;	    	
	}
 	
}

六、完整代码

#include<cstdio>
#include<cstdlib>
#include<malloc.h>
#include<cstring>
#include<conio.h>
//函数结果状态代码 
#define TRUE  1
#define FALSE 0
#define OK    1
#define ERROR 0 

typedef int Status;  //int 的别名,其值是函数结果状态代码 

typedef struct LNode{
	int            data;
	struct LNode * next; //指向结构体类型的指针,表示下一个结点 
}LNode,* LinkList;

/*  最后一句的等同于下面代码,中括号是我自己加上的,只是提示把他们看作是一个整体
 
typedef  {struct  LNode   }  LNode;      // struct  LNode 的别名是 LNode
 
typedef  {struct  LNode * }  LinkList;   // struct  LNode *  的别名是 LinkList

*/
 
 //1.有头结点 
 Status create_headLinked_List(LinkList & L,int n) 
 //逆序输入n个元素的值,建立带表头结点单链线性表L 
 {
 	LinkList head = (LinkList)malloc(sizeof(LNode));//头结点的存储地址 
 	if(NULL == head) return ERROR;                  //分配空间失败 
 	L = head;                                       //头指针L存储头结点P的地址,即头指针指向头结点 
 	L->data = 0;                                    //用头结点统计元素个数,刚开始为0个 
 	L->next = NULL;                                 //此时链表为空 
 	for(int i = 0;i<n;i++)
	 {
 		LinkList List_Node = (LinkList)malloc(sizeof(LNode));
 		scanf("%d",&List_Node->data);
 		L->data++;                               //每插入一个元素,头结点统计链表元素个数加一 
 		List_Node->next = L->next;               //头插法,即链表中的元素顺序与输入相反 
 		L->next = List_Node;
	 } 
	 return OK;
 }
 
 //2.尾插法创建带头结点的链表 
 Status tail_create_heahLinked_List(LinkList & L,int n) 
 {
 	LinkList head = (LinkList)malloc(sizeof(LNode));//头结点的存储地址 
 	if(NULL == head) return ERROR;                  //分配空间失败 
 	L = head;                                       //头指针L存储头结点P的地址,即头指针指向头结点 
 	L->data = 0;                                    //用头结点统计元素个数,刚开始为0个 
 	L->next = NULL;                                 //此时链表为空
	LinkList temp = L;                               
 	for(int i = 0;i<n;i++)
	 {
 		LinkList List_Node = (LinkList)malloc(sizeof(LNode));
 		scanf("%d",&List_Node->data);
 		L->data++;  
		List_Node->next = NULL;               //每插入一个元素,头结点统计链表元素个数加一 
 		temp->next = List_Node;               //尾插法,即链表中的元素顺序与输入相同 
 		temp = temp->next;
	 } 
	 return OK;
 }
 
 
 //3.查找某一个数据是否在链表中,如果没有,返回false,否则返回该结点的位序
 
 Status  getLNodeIndex(LinkList L,int number)
 {
 	int i=1;
 	LinkList  temp =L->next;
 	while(temp)
	 {
 		if(number == temp->data) return i;
 		temp = temp->next;
 		i++;
	 }
	 return FALSE;
 }
 
 //4.获取链表长度
 Status getLength(LinkList L)
 {
 	return L->data;
 } 
 
 //5.删除某个位置节点 (从1开始) 
 
 Status  deleteLnode(LinkList L ,int i)
 {
 	LinkList temp = L;
 	int j=1;
	while(temp->next && j<i )
	{
		temp = temp->next;
		j++;
	}  
	if(NULL == temp->next || j > i)  return ERROR;//循环结束,若最终待删除结点为空,或i为0或负数时,说明位置不合理,直接返回 
	LinkList p = temp->next; //p是待删除结点
	temp->next = p->next;    //将p的下一个结点地址赋值给p的前一个结点 
	free(p);
	L->data--;
	return OK;
 } 
 
  
  
  
  
//6.在第i 个位置之前插入新结点
Status  insertLnode(LinkList L ,int i,int value)
{
	LinkList temp = L;
	int j=1;
	LinkList newLnode = (LinkList)malloc(sizeof(LNode));
	while(j<i)
	{
		temp = temp->next;
		j++;
	}
	
	if( j > i || i>L->data + 1 )   return ERROR;//允许在最后一个元素之后插入元素 
	newLnode->data = value;
	newLnode->next = temp->next;
	temp->next = newLnode;
	L->data++;
	return OK;
} 



 //7.输出带头结点链表中的元素 
 void headlinked_List_Traverse(LinkList  L)
 {
 	
    printf("该链表的元素个数为:%d \n",L->data);
	LinkList p = L->next;    
 	while(p)
	{    
 	    printf(" %d ",p->data);	
		p = p->next;	    	
	}
 	
}

//8.修改第i个结点的值

Status  updateLnode(LinkList L,int i,int value)
{
	LinkList temp = L;
 	int j=1;
	while(temp->next && j<i )
	{
		temp = temp->next;
		j++;
	}  
	if(NULL == temp->next || j > i)  return ERROR;//循环结束,若最终待修改结点为空,或i为0或负数时,说明位置不合理,直接返回 
    temp->next->data = value; //结点
	return OK;
} 



//销毁链表,包括头结点的所有结点全部释放	 
Status destroy_headlinked_list(LinkList  L)	
{
	L->data = 0; 
	LinkList p;
	while(L)
	{   
	    p = L->next;
	    free(L);
		L = p;			    	
	} 
 	return OK;
} 

/*清空带头结点链表
  1.先保留链表的头结点
  2.然后把头结点后面的所有的都销毁
  3.最后把头结点里指向首元结点的指针设为空 */
Status clear_headlinked_list(LinkList  L)	
{
	L->data = 0; 
	LinkList p,q;
	p = L->next;
	while(L)
	{   
	    q = p->next;
	    free(p);
		p = q;			    	
	} 
	L->next = NULL;
 	return OK;
}  




int main(void)
{
    LinkList L;//头指针L 
    int n = 0;
    printf("请输入所要创建带头结点链表的元素个数:");
    scanf("%d",&n);
    if( tail_create_heahLinked_List( L, n) )
    headlinked_List_Traverse(L);
    int number=0;
    printf("\n请输入查找的元素:");
    scanf("%d",&number);
    if(getLNodeIndex(L,number))	
        printf("%d",getLNodeIndex(L, number));
    else printf("未找到! ");
    
    printf("\n该链表的长度为:%d",getLength(L));

    int i=1,value;
    printf("\n请输入删除结点的位置:");
    scanf("%d",&i); 
    deleteLnode( L ,i); 
    headlinked_List_Traverse(L);
    printf("\n该链表的长度为:%d",getLength(L));
     
    printf("\n请输入插入结点的位置和数据:");
    scanf("%d %d",&i,&value); 
    insertLnode( L ,i, value);
    printf("\n该链表的长度为\n:%d",getLength(L));
    headlinked_List_Traverse(L);
    
    printf("\n请输入待修改结点的位置和数据:");
    scanf("%d %d",&i,&value); 	
    updateLnode(L,i, value);
    headlinked_List_Traverse(L);
	
    if(destroy_headlinked_list(L))
	printf("\n销毁带头结点链表L成功!\n");

    return 0;
} 
  

七、总结

1.链表的优缺点

  优点:插入结点和删除结点效率高。不要求逻辑相邻物理位置必须相邻,位置可以分散。扩展性好。

  缺点:不支持随机存取,要访问某一个结点,需要找到前驱结点。占用内存大,需要保存后继信息。

2.数组的优缺点

优点:支持随机存取,查找某个位置元素快速,占用内存小。

缺点:插入和删除元素需要移动大量元素,扩展性差。

3.链表操作注意事项

1.循环注意判断临界点的值。

2.在进行插入操作时,注意顺序。

3.利用好头结点。

4.注意是否改变头指针的值,要明确什么时候使用头指针引用(二级指针可以代替)

猜你喜欢

转载自blog.csdn.net/xiaohaiguang/article/details/105623350
2.2