数据结构——线性表链式存储

目录

一、链式存储结构定义

二、链表的介绍

三、循环链表的实现

1、循环链表的创键

2、 链表初始化

3、 插入算法(头插法)

4、 插入算法(尾插法)

5、 插入算法(位置i插入)

6、 查找元素

7、求链表长度

8、 输出元素

四、完整Demo 

五、小结 

六、参考文献 


一、链式存储结构定义

1、定义:线性表的存储方式分为顺序存储和链式存储。其中链式存储是指将线性表中的元素按照一定的次序链接起来,每个元素包含两个部分:数据域和指针域。其中,数据域用于存储元素的值,指针域用于指向下一个元素的地址。通过指针域之间的链接,可以将线性表中的元素连接成一条链式结构。

2、特点:

  • 结点在存储器中的位置是任意的,即逻辑上相邻的数据元素在物理上不一定相邻。
  • 访问时只能通过头指针进入链表,并通过每个结点的指针域依次向后顺序扫描其余结点,所以寻找到一个结点和最后一个结点所花费的时间不等。

二、链表的介绍

1、链表分为单链表、循环链表、双向链表 。 

  •  单链表: 每个结点只包含直接后继的地址信息,结点只有一个指针域的链表。
  • 循环链表:单链表的最后一个结点的直接后继为第一给结点(首尾相接)
  •  双向链表:单链表中的结点包含直接前驱和后继的地址信息(结点有两个指针域 )                                   

链表中相关术语:

  1. 头结点:它是链表中的辅助结点,只包含指向第0个数据元素的指针,而没有数据信息,头结点有简化代码的作用,因为它始终指向了第0个元素,便于执行时对元素位置的定位。
  2. 尾结点:尾结点中存储的地址信息可以用于区分链表类型。尾指针为空:单链表。尾指针指向链表的开头:循环链表。为随机值:非法链表。
  3. 数据结点:它是链表中代表数据元素的结点,表现为数据域和指针域。
  4. 指针域:结点中存储数据元素之间的链接信息即下一个结点地址的部分。
  5. 数据域:结点中存储数据元素的部分。

三、循环链表的实现

1、循环链表的创键

循环链表是一种头尾相接的链表如上图,表中最后一个结点的指针域指向头结点,整个链表形成一个一个环状,循环链表与单链表类似,只不过单链表最后一个结点指向NULL.

/*简单链表的定义*/
typedef struct node    //声明结点的类型和指向结点的指针类型
{
	DataType		data;	/*数据域*/
	struct node		*next;	/*指针域*/
}SingleLinkList, SingleLinkNode;
2、 链表初始化

        单链表的初始化是指将一个空的单链表开辟一段连续的内存空间,生成新的结点作为头结点,用头指针指向头结点,并将头结点指针域置空。具体代码实现如下:

//init函数接受一个指针的指针SingleLinkList **Head 作为参数表示需要初始化单链表的头结点地址
int init(SingleLinkList **Head)   
{
	if(1)
	{
		/*使用malloc()函数为单链表分配一块空间,大小为 sizeof(SingleLinkList)*/
		(*Head) = (SingleLinkList*)malloc(sizeof(SingleLinkList));
		/*判断内存申请是否成功*/
		if(*Head == NULL)
		{
			printf("申请内存错误, 初始化失败![100001]\n");
			return 100001;
		}
        /*将头结点的next指针指向自身,即(*Head)->next=*Head*/
		(*Head)->next=*Head;
		/*单链表:(*Head)->next = NULL;*/
		/*循环链表:(*Head)->next = *Head;*/
		return 0;
	}
	else
	{
		printf("该链表已经初始化!请删除后再执行此操作![100002]\n");
		return 100002;
	}
}
3、 插入算法(头插法)

        头插法是将新的元素插入到头结点的后面。具体是先将头结点与首节点联系断开,将首节点的地址(存放在头结点的指针域)赋给要插入的新节点的指针域,这样子新节点就和后面的所有节点连接了起来,起到了保护后面所有节点的作用; 然后将新节点的地址赋值给头结点的指针域,覆盖掉原本存放的首节点的地址,这样操作就完成了对链表的头结点插入元素。

/*2. 插入元素,头插法*/
int insert_head(SingleLinkList **Head, DataType x)
{
	SingleLinkNode *newNode;

	if(0)
	{
		printf("链表未初始化![100003]\n");
		return 100003;
	}
	
	newNode = (SingleLinkNode*)malloc(sizeof(SingleLinkNode));
	if(!newNode)
	{
		printf("申请节点内存空间失败![100004]\n");
		return 100004;
	}
	newNode->data = x;
	newNode->next = (*Head)->next;

	(*Head)->next = newNode;

	return 0;
}
4、 插入算法(尾插法)

        循环链表的尾部插入一个新元素和单链表尾部插入新元素有所不同,前者最后一个元素指向的是头结点,而单链表的最后一个元素指向的是NULL。先通过遍历找到一个指向链表头部的指针,然后修改尾结点的next指针,使其执行循环链表的头部,最后更新尾指针,使其指向新节点。

/*3. 插入元素, 尾插法*/
int insert_tail(SingleLinkList **Head, DataType x)
{
	SingleLinkNode *newNode;
	SingleLinkNode *p;

	if(0)
	{
		printf("链表未初始化![100003]\n");
		return 100003;
	}
	
	newNode = (SingleLinkNode*)malloc(sizeof(SingleLinkNode));
	if(!newNode)
	{
		printf("申请节点内存空间失败![100004]\n");
		return 100004;
	}

	newNode->data = x;
	/*单链表newNode->next = NULL;*/
	newNode->next=*Head;
	/*循环链表 newNode->next = *Head;*/

	p = (*Head);

	while(p->next==Head)
	{
		p = p->next;
	}

	p->next = newNode;

	return 0;
}
5、 插入算法(位置i插入)

        首先,定义一个指针变量,初始化为头结点,需要找到插入节点的前一个节点。因为,要插入的位置的前一个节点保存了要插入位置的后一个节点的地址知道了后面节点的地址才能对其进行操作。

/*4. 插入元素,在位置i处插入元素x */
int insert(SingleLinkList **Head, int i, DataType x)
{
	int j;
	SingleLinkNode *p;
	SingleLinkNode *newNode;

	
	/*对i进行判断,0<i<=length+1*/
	if(i<1 || i>length(*Head)+1)
	{
		printf("位置i不是链表有效位置![100005]\n");
		return 100005;
	}
	p = (*Head);
	j = 1;

	while(j<i)
	{
		j++;
		p = p->next;
	}

	newNode = (SingleLinkNode*)malloc(sizeof(SingleLinkNode));
	/*此处省略检测newNode是否申请成功*/

	newNode->data = x;
	newNode->next = p->next;
	p->next = newNode;
	return 0;
}
6、 查找元素

        通过一个循环来从链表头部开始遍历,找到所需元素。如果在遍历过程中找到了该元素,则返回该元素所在的结点。

/*5. 查找值为x的元素,返回位置i */
int find(SingleLinkList *Head, DataType x)
{
	int i;
	SingleLinkNode *p;

	i = 1;
	p = Head->next;

	while( p!= Head && p->data != x)  /*单链表while(p && p->data != x) */
	{
		i++;
		p = p->next;
	}

	if(p == Head)  /*单链表if(!p) */
	{
		return 0;
	}
	else
	{
		return i;
	}

}
7、求链表长度

        循环链表是一种特殊的链表,其最后一个节点的next域不是NULL,而是指向头节点。因此,循环链表的长度是指从头节点开始,到某个节点再次回到头节点所经过的结点数。如果从头节点开始,经过n个结点后再次回到头节点,则循环链表的长度为n。

/*6. 链表长度*/
int length(SingleLinkList *Head)
{
	int len=0;
	SingleLinkNode *p;

	p = Head->next;
	while(p!=Head) /*单链表*while(p)*/	
	{
		len++;
		p = p->next;
	}
	return len;
}
8、 输出元素

        通过指针的移动来访问链表的每一个结点,链表的结束遍历标志为遍历到尾结点,即当一个结点的指针域为Head的时候,就找到了尾结点,链表的遍历也就完成了,输出结果完成。

/*7.输出链表*/
void print(SingleLinkList *Head)
{
	SingleLinkNode *p;
	int i=0;

	p = Head->next;
	if(p==Head)/*单链表if(!p)*/			
	{
		printf("链表为空!\n");
		return;
	}
	while(p!=Head)/*while(p)*/ 
	{
		printf("Node[%d]. = %d\n", ++i, p->data);
		p = p->next;
	}
}

四、完整Demo 

(1)main.c

#include <stdio.h>
#include <string.h>
#include "SingleLinkList.h"
#include <stdlib.h>
#include "welcome.h"


int main(int argc, char* argv[])
{
	SingleLinkList *Head;
	DataType x;
	int i,m,n,cmd;

	for(i=0;i<strlen(welcome);i++)
	{
		printf("%c",welcome[i]);
		for(m=0;m<1000;m++)
			for(n=0;n<1000;n++)
			{
				;
			}
	}

	printf("-----------简单链表演示程序----------\n");
	do
	{
		printf("1. 初始化链表表\n");
		printf("2. 插入元素(头插法)\n");
		printf("3. 插入元素(尾插法)\n");
		printf("4. 插入元素(在位置i插入)\n");
		printf("5. 查找元素x\n");
		printf("6. 求链表长度\n");
		printf("7. 输出链表\n");
		printf("8. 删除元素\n");
		printf("10. 帮助\n"); 
		printf("0. 退出\n");
		printf("请输入您要进行的操作(1~6,0退出):");
		scanf("%d", &cmd);
		switch(cmd)
		{
		case 1:
			if(!init(&Head))
			{
				printf("链表已初始化!\n");
			}
			break;
		case 2:
			printf("请输入插入元素x:x=");
			scanf("%d",&x);
			if(!insert_head(&Head,x))
			{
				printf("元素(%d)已插入\n", x);
			}
			break;
		case 3:
			printf("请输入插入元素x:x=");
			scanf("%d",&x);
			if(!insert_tail(&Head,x))
			{
				printf("元素(%d)已插入\n", x);
			}
			break;
		case 4:
			printf("请输入插入元素位置i和元素x(i,x):");
			scanf("%d,%d", &i, &x);
			if(!insert(&Head, i, x))
			{
				printf("已在位置(%d)插入元素(%d)!\n",i, x);
			}
			break;
		case 5:
			printf("请输入要查找的元素x:");
			scanf("%d", &x);
			if(i = find(Head,x))
			{
				printf("元素%d存在,在链表位置%d.\n", x, i);
			}
			else
			{
				printf("在链表中未找到元素x。\n");
			}
			break;
		case 6:
			printf("链表的长度为:%d\n", length(Head));
			break;
		case 7:
			print(Head);
			break;
		case 8:
			printf("请输入要删除的元素x:");
			scanf("%d", &x);
			if(!delete(&Head, x))
			{
				printf("元素x【%d】已删除!\n", x);
			}
			break;
		case 10:
			
			printf(" 本程序为链表的演示程序,有ZZB设计开发,程序完成了循环表插入、删除、查找等功能!\n");
			break;
			
		}
	}while(cmd != 0);

	return 0;
}

(2)SingleLinkList.c

/*
	SingleLinkList.c
	
*/
#include "SingleLinkList.h"
#include <stdlib.h>
#include <stdio.h>
/*#include<stdbool.h>*/

/*1. 初始化*/
int init(SingleLinkList **Head)
{
	if(1)
	{
		/*申请内存*/
		(*Head) = (SingleLinkList*)malloc(sizeof(SingleLinkList));
		/*判断内存申请是否成功*/
		if(*Head == NULL)
		{
			printf("申请内存错误, 初始化失败![100001]\n");
			return 100001;
		}
		(*Head)->next=*Head;
		/*单链表:(*Head)->next = NULL;*/
		/*循环链表:(*Head)->next = *Head;*/
		return 0;
	}
	else
	{
		printf("该链表已经初始化!请删除后再执行此操作![100002]\n");
		return 100002;
	}
}


/*2. 插入元素,头插法*/
int insert_head(SingleLinkList **Head, DataType x)
{
	SingleLinkNode *newNode;

	if(0)
	{
		printf("链表未初始化![100003]\n");
		return 100003;
	}
	
	newNode = (SingleLinkNode*)malloc(sizeof(SingleLinkNode));
	if(!newNode)
	{
		printf("申请节点内存空间失败![100004]\n");
		return 100004;
	}
	newNode->data = x;
	newNode->next = (*Head)->next;

	(*Head)->next = newNode;

	return 0;
}


/*2. 插入元素, 尾插法*/
int insert_tail(SingleLinkList **Head, DataType x)
{
	SingleLinkNode *newNode;
	SingleLinkNode *p;

	if(0)
	{
		printf("链表未初始化![100003]\n");
		return 100003;
	}
	
	newNode = (SingleLinkNode*)malloc(sizeof(SingleLinkNode));
	if(!newNode)
	{
		printf("申请节点内存空间失败![100004]\n");
		return 100004;
	}

	newNode->data = x;
	/*单链表newNode->next = NULL;*/
	newNode->next=*Head;
	/*循环链表 newNode->next = *Head;*/

	p = (*Head);

	while(p->next==Head)
	{
		p = p->next;
	}

	p->next = newNode;

	return 0;
}


/*3. 插入元素,在位置i处插入元素x */
int insert(SingleLinkList **Head, int i, DataType x)
{
	int j;
	SingleLinkNode *p;
	SingleLinkNode *newNode;

	
	/*对i进行判断,0<i<=length+1*/
	if(i<1 || i>length(*Head)+1)
	{
		printf("位置i不是链表有效位置![100005]\n");
		return 100005;
	}
	p = (*Head);
	j = 1;

	while(j<i)
	{
		j++;
		p = p->next;
	}

	newNode = (SingleLinkNode*)malloc(sizeof(SingleLinkNode));
	/*此处省略检测newNode是否申请成功*/

	newNode->data = x;
	newNode->next = p->next;
	p->next = newNode;
	return 0;
}


/*4. 删除元素, 删除值为x的元素*/
int delete(SingleLinkList **Head, DataType x)
{
	int i;
	int j;

	SingleLinkNode *p;
	SingleLinkNode *q; /*要删除的元素x*/

	i = find(*Head,x);
	if(!i)
	{
		printf("元素x【%d】不存在!100006\n", x);
		return 100006;
	}
	
	p = (*Head);
	j=1;

	while(j<i)
	{
		j++;
		p = p->next;
	}
	
	q = p->next;
	p->next = q->next;

	free(q); /*释放内存*/

	return 0;
}


/*5. 查找值为x的元素,返回位置i */
int find(SingleLinkList *Head, DataType x)
{
	int i;
	SingleLinkNode *p;

	i = 1;
	p = Head->next;

	while( p!= Head && p->data != x)  /*单链表 while(p && p->data != x)*/
	{
		i++;
		p = p->next;
	}

	if(p == Head)  /*单链表if(!p) */
	{
		return 0;
	}
	else
	{
		return i;
	}

}

/*6. 链表长度*/
int length(SingleLinkList *Head)
{
	int len=0;
	SingleLinkNode *p;

	p = Head->next;
	while(p!=Head) /*单链表*while(p)*/	
	{
		len++;
		p = p->next;
	}
	return len;
}

/*7.输出链表*/
void print(SingleLinkList *Head)
{
	SingleLinkNode *p;
	int i=0;

	p = Head->next;
	if(p==Head)/*单链表if(!p)*/			
	{
		printf("链表为空!\n");
		return;
	}
	while(p!=Head)/*while(p)*/ 
	{
		printf("Node[%d]. = %d\n", ++i, p->data);
		p = p->next;
	}
}

(3)SingleLinkList.h 

/*
	SingleLinkList.h
*/

typedef int DataType;

/*简单链表的定义*/
typedef struct node
{
	DataType		data;	/*数据域*/
	struct node		*next;	/*指针域*/
}SingleLinkList, SingleLinkNode;


/*1. 初始化*/
int init(SingleLinkList **Head);

/*2. 插入元素,头插法*/
int insert_head(SingleLinkList **Head, DataType x);

/*2. 插入元素, 尾插法*/
int insert_tail(SingleLinkList **Head, DataType x);

/*2. 插入元素,在位置i处插入元素x */
int insert(SingleLinkList **Head, int i, DataType x);

/*3. 删除元素, 删除值为x的元素*/
int delete(SingleLinkList **Head, DataType x);

/*5. 查找值为x的元素,返回位置i */
int find(SingleLinkList *Head, DataType x);

/*6. 求链表的长度 */
int length(SingleLinkList *Head);

/*7.输出链表*/
void print(SingleLinkList *Head);



(4)welcome.h 

char welcome[] = "\n\
           /\ \n\
          ( *)======/\==== \n\
           )(      /  \ \n\
__________/  )    /    \ \n\
\___         /   / \"\"   \ \n\
  \____    _/   / (**)   \ \n\
     / \__/    (----------) \n\
    /____|__//_ ( 本程序 ) \n\
         |       ( 由ZZB) \n\
         |       ( 开发 )\n\
         |        (____)\n\
        _|__\n\
         \\    ☆新年 . 快乐☆\n\n";

 10.运行结果

 ​ 

五、小结 

  1. 循环链表的任意元素都有一个前驱和一个后继,所有数据元素在关系上构成逻辑上的环。循环链表是一种特殊的单链表,尾结点的指针指向首结点的地址。

  2. 结点在存储器中的位置是任意的,即逻辑上相邻的数据元素在物理上不一定相邻。
  3. 访问时只能通过头指针进入链表,并通过每个结点的指针域依次向后顺序扫描其余结点,所以寻找到一个结点和最后一个结点所花费的时间不等。

六、参考文献 

《数据结构》(C语言版)李刚,刘万辉.北京:高等教育出版社 ,2017.   

《C语言程序设计》(第四版)谭浩强. 北京:清华大学出版社,2014.

  CSDN 数据结构-----循环链表

  • 1
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值