数据结构-线性表-链式存储结构-单链表

数据结构-线性表-链式存储结构-单链表_第1张图片

 

一、前言

二、顺序表知识点回顾

三、单链表

1.链表的定义

2.单链表的分类

3.单链表尾结点说明

4.单链表基本运算算法

5.快速创建链表

6.循环单链表

四、总结

一、前言

通过上篇文章(线性表中顺序表_m0_50708613的博客-CSDN博客),了解到了数据结构中的线性表有顺序存储和链式存储两种结构,本章主要讲解链式存储结构中单链表和双链表。

二、顺序表知识点回顾

顺序表在逻辑上相邻两个元素在物理上也是相邻的,在存取某一个元素时很容易,而在插入和删除元素时,需要移动大量元素的位置,导致算法的运算量大,时间复杂度大,但是线性表是链式存储结构却可以解决此问题。

顺序表知识点补充——整体创建链表


//整体创建顺序表
void CreateList(SqList &L,ElemType a[],int n)
{
	int j=0;
	for(int i=0;i

其时间复杂度为O(n)。

三、单链表

1.链表的定义

每一个结点(node)存放一个数据元素,并用一个指针表示结点间的逻辑结构。如图所示:

数据结构-线性表-链式存储结构-单链表_第2张图片

date即数据域,用于存放结点(node)的值。

next即指针域或链域,用于存放后继结点的地址。

2.单链表的分类

单链表可分为带头结点和不带头结点两种类型。

3.单链表尾结点说明

单链表的尾结点设计有两种方式

(1)将尾结点的指针域(next)用一个空指针表示,其不指向任何结点,仅仅起到标志作用,故称之为非循环单链表。如图所示:

数据结构-线性表-链式存储结构-单链表_第3张图片

(2)将尾结点的指针域(next)指向头结点,形成环型,也称之为循环单链表。如图所示:

数据结构-线性表-链式存储结构-单链表_第4张图片

4.单链表基本运算算法

(1)代码详解:

#include 
#include 
//结点类型声明
typedef int ElemType;
typedef struct node
{
	ElemType data;     //数据域data
	struct node *next;   //指针域
}LNode;                 //单链表结点类型
//初始化单链表运算算法
void InitList(LNode *&L)             //L为引用型参数
{
	L=(LNode*)malloc(sizeof(LNode));  //申请空间,创建头结点L
	L->next=NULL;                     //头结点的指针域(next)指向NULL,表示空链表

}
//销毁单链表的运算算法
void DestroyList(LNode *&L)
{
	LNode *p1=L,*p=p1->next;
	while(p!=NULL)
	{
		free(p1);        //释放p1结点空间
		p1=p;p=p->next;  //p1,p同步后移
	}
	free(p1);           //释放p1指向的尾结点空间
}
//求单链表的长度运算算法
int GetLength(LNode *L)
{
	int i=0;
	LNode *p=L->next;         //p指向头结点,i为1
	while(p !=NULL)
	{
		i++;
		p=p->next;           //p移到下一个结点,i++
	}
	return i;                //p为空时,i即为数据结点个数
}
//求单链表中第i个元素运算算法
int GetElem(LNode *L,int i,ElemType &e)
{
	int j=0;
	LNode *p=L;     //p指向头结点,计数器j值为0
	if(i<0)
		return 0;   //参数i错误返回0
	while(p!=NULL && jnext;
	}
	if(p==NULL)
		return 0;    //未找到返回0
	else
	{
		e=p->data;
		return 1;   //找到后返回1
	}
}
//按值查找运算算法
int Locate(LNode *L,ElemType e)
{
	LNode *p=L->next;
	int j=1;               //p指向头结点,j置为序号1
	while(p!=NULL && p->data!=e)
	{
		p=p->next;
		j++;
	}
	if(p==NULL)
		return(0);           //未找到返回0
		else
		return (j);         //找到后返回其序号
}
//插入元素运算算法
int InsElem(LNode * &L,ElemType x,int i)  //插入结点值为X的结点
{
	int j=0;
	LNode *p=L,*s;
	if(i<=0)
		return 0;         //参数i错误返回0
	while(p!=NULL && jnext;
	}
	if(p==NULL)
		return 0;          //未找到第i-1个结点是返回0
	else                    //找到第i-1个结点p
	{
		s=(LNode*)malloc(sizeof(LNode));
		s->data=x;         //创建存放元素x的新结点s
		s->next=p->next;   //将s结点插入到P结点之后
		p->next=s;
		return 1;          //插入成功,返回1
	}
}
//删除结点运算算法
int DelElem(LNode *&L,int i)
{
	int j=0;
	LNode *p=L,*q;
	if(i<=0)
		return 0;             //参数i错误返回0
	while(p!=NULL && jnext;
	}
	if(p==NULL)
		return 0;            //未找到第i-1个结点是返回0
	else                     //找到第i-1个结点p
	{
		q=p->next;          //q指向被删除的结点
		if(q==NULL)
			return 0;       //没有第i个结点是返回0
		else
		{
			p->next=q->next; //从单链表中删除q结点
			free(q);         //释放其空间
			return 1;
		}
	}
}
//输出单链表
void DispList(LNode *L)
{
	LNode *p=L->next;
	while(p!=NULL)
	{
		printf("%d",p->data);
		p=p->next;
	}
	printf("\n");
}

void main()
{
	int i;
	ElemType e;
	LNode *L;
	InitList(L);
	InsElem(L,6,1);
	InsElem(L,4,2);
	InsElem(L,4,3);
	InsElem(L,7,4);
	InsElem(L,1,5);
	InsElem(L,9,6);
	InsElem(L,8,7);
	InsElem(L,3,8);
	InsElem(L,2,9);
	InsElem(L,7,10);
	printf("单链表: ");
	DispList(L);
	printf("长度: %d\n",GetLength(L));
	i=6;
	GetElem(L,i,e);
	printf("第%d个元素: %d\n",i,e);
	e=3;
	printf("元素%d是第%d个元素\n",e,Locate(L,e));
	i=7;
	printf("删除第%d个元素\n",i);
	DelElem(L,i);
	printf("单链表:");
	DispList(L);
	DestroyList(L);
}

(2)结果演示

数据结构-线性表-链式存储结构-单链表_第5张图片

(3) 单链表基本运算算法时间复杂度分析

基本运算算法 时间复杂度
void InitList(LNode *&L)——初始化 O(1)
void DestroyList(LNode *&L)——销毁 O(n)
int GetLength(LNode *L)——求单链表的长度 O(n)
求单链表中第i个元素
int GetElem(LNode *L,int i,ElemType &e)
O(n)
int Locate(LNode *L,ElemType e)——查找 O(n)
int InsElem(LNode * &L,ElemType x,int i) ——插入 O(n)
int DelElem(LNode *&L,int i)——删除 O(n)
void DispList(LNode *L)——输出单链表 O(n)

5.快速创建链表

a:头插法

#头插法
void CreateListF(LNode *&L,ElemType a[],int n)
{
	LNode *s;
	L=(LNode*)malloc(sizeof(LNode));   //申请空间
	L->next=NULL;                      //创建一个空单链表
	for(int i;idata=a[i];                 //将创建存放a[i]元素的新节点s
		s->next=L->next;              //将s结点插入到头结点
		L->next=s;
	}
}

注:建成 的单链表结点次序与插入次序相反。

b:尾插法

//尾插法
void CreateListR(LNode *&L,ElemType a[],int n)
{
	LNode *s,*d;
	L=(LNode *)malloc(sizeof(LNode));  //创建头结点
	d=L;                              //d始终指向尾结点,初始时指向头结点
	for(int i=0;idata=a[i];                 //创建存放a[i]元素的新节点s
		d->next=s;                   
		d=s;                         //结点s变成新的尾结点,
	}
	d->next=NULL;                    //由于是普通的单链表,故尾结点next域置为NULL
}

注:建成 的单链表结点次序与插入次序相同。

6.循环单链表

循环单链表与普通单链表非常相似,只是将单链表中尾结点的next域有原来的NULL改为指向头结点,其基本运算算法极其相似。下面给出循环单链表的基本运算算法。

typedef int ElemType;
//类型声明
typedef struct node
{
	ElemType data;
	struct node *next;
}LNode;
//初始化循环单链表运算算法
void InitList(LNode *&L)
{
	L=(LNode*)malloc(sizeof(LNode));
	L->next=L;                      //尾结点指向头结点,不再指向NULL

}
//销毁循环单链表运算算法
void DestroyList(LNode *&L)
{
	LNode *p1,*p=p->next;
	while(p1!=L)
	{
		free(p1);
		p1=p;
		p=p->next;
	}
	free(p1);
}
//求循环单链表的长度运算算法
int GetLength(LNode *L)
{
	int i=0;
	LNode *p=L->next;
	while(p!=L)
	{
		i++;
		p=p->next;
	}
	return i;
}
//求循环单链表中第i个元素运算算法
int GetElem(LNode *L,int i,ElemType &e)
{
	int j=1;
	LNode *p=L->next;
	if(i<=0)
		return 0;
	while(p!=L && jnext;
	}
	if(p==L)
		return 0;
	else
	{
		e=p->data;
		return 1;
	}
}
//按值查找运算算法
int Locate(LNode *L,ElemType x)
{
	int i=1;
	LNode *p=L->next;
	while(p!=L && p->data!=x)       //从头结点开始查找data域为x的结点
	{
		p=p->next;
		i++;
	}
	if(p==L)
		return 0;           //未找到值为x的结点返回0
	else
		return i;             //找到第一个值为x的结点并返回i
}
//插入元素运算算法
int InsElem(LNode *&L,ElemType x,int i)
{
	int j=1;
	LNode *p1=L,*p=p1->next,*s;
	if(i<=0)
		return 0;                     //参数i错误返回0
	while(p!=L && jnext;
	}
	if(p==L && i>j+1)
		return 0;                   //参数i>n+1是错误返回0
	else                            //成功查找到底i个结点是前驱结点
	{
		s=(LNode*)malloc(sizeof(LNode));
		s->data=x;
		s->next=p1->next;
		p1->next=s;
		return 1;
	}
}
//删除运算算法

int DelElem(LNode *&L,int i)
{
	int j=0;
	LNode *p=L,*p2;                //p指向头结点
	if(i<=0)
		return 0;                  //参数i错误返回0
	while(p->next !=L && jnext;
	}
	if(p->next==L)
		return 0;                //未找到返回0
	else
	{
		p2=p->next;              //p2指向被删结点
		if(p2==L)
			return 0;            //没有第i个结点是返回0
		else
		{
			p->next=p2->next;    //从循环单链表中删除p2结点
			free(p2);            //释放其空间
			return 1;            //成功删除返回1
		}
	}
}
//输出循环单链表运算算法
void DispList(LNode *L)
{
	LNode *p=L->next;
	while(p!=L)
	{
		printf("%d",p->data);
		p=p->next;
	}
	printf("\n");
}

四、总结

单链表的物理存储位置是随机的,没有一一对应的逻辑关系,在插入和删除运算时,必备大量的移动数据域的位置,只要设计一个新指针进行遍历便好。而且创建链表有两种快速的方法,可选择性提高了,可根据情况选择对应的方法创建链表解决问题。

你可能感兴趣的:(数据结构,链表,散列表)