【数据结构】(C语言版)第二章:线性表

一、顺序表

二、单链表

1.概念:

每个结点,除了存放自身数据外,还需要存放一个指向其后继的指针。
单链表是非随机存取的,查找某个结点时,需要从表头开始遍历。
头结点与头指针的区别:

不管带不带头结点,头指针始终指向链表的第一个结点。而头结点是带头结点的第一个结点。
通常带头结点的链表更方便,在插入或删除时不用再进行特殊判断。

1.定义

typedef struct LNode{
	ElemType data;
	struct LNode * next;
}LNode,*LinkList;

2.创建

创建带头节点的单链表、不带头节点的单链表(空链表)

// 带头结点
bool InitList(LinkList &L){
	L=(LNode *)malloc(sizeof(Lnode));
	if(L==NULL)
		return false;//内存不足,分配失败
	L->next=null;
	return true;	
}
// 不带头结点
bool InitList(LinkList &L){
	L=null;
	return true;
}

3.插入

(1)头插法建立单链表:

采用头插法时,输入顺序与生成的链表中的元素顺序是相反的。

LinkList List_HeadInsert(LinkList &L){//逆向建立单链表
	Lnode *s;
	int x;
	L=(LinkList)malloc(sizeof(LNode));//创建头结点
	L->next=NULL;//创建初始为空的链表,初始为空

	scanf("%d",&x);
		while(x!=9999){//输入9999表示结束
		s=(LNode *)malloc(sizeof(LNode));
		s->data=x;
		//将新结点插入表中,L为头结点
		s->next=NULL;
		s->next=L->next;
		
		L->next=s;
		scanf("%d",&x);
		}
return L;
}

(2)尾插法建立单链表

能够保持输入顺序与链表中的元素顺序一致。
为此必须增加尾指针r,始终指向最后一个元素。

LinkList List_TailInsesrt(LinkList &L){
	int x;//设置元素类型为整型
	L=(LinkList)malloc(sizeof(LNode));//创建头结点
	Lnode *s, *r=L;//尾指针初始时指向头结点
	scanf("%d",&x);
	while(x != 9999){
		s=(LNode *)malloc(sizeof(LNode));
		s->data=x;
		
		r->next=s;	//r指向新的表尾结点
		r=s;
		scanf("%d",&x);
	}
	r->next=NULL;
	return L;
}

(3)插入结点(在其前驱结点进行后插操作)

在这里插入图片描述

方法一:查找前驱结点+后插的代码如下:

bool ListInsert(LinkList &L,int i,ElemType e){
	if(i<1)
		return false;
	int j=0;	//当前p指向第几个结点
	Lnode *p=L;		//L指向头结点,将L赋值给p,p指向头结点,头结点为第0个结点
	while(p!=NULL && j < i-1){	//循环找到第i-1个结点
		p=p->next;
		j++;
	}
	//在p结点进行后插操作
	return InsesrtNextNode(p,e);
}

方法二:或者封装两个函数,一是按位查找结点,二是在指定结点作后插方法。

LNode *p=GetElem(L,i-1);	//查找第i-1个结点
InsesrtNextNode(p,e);		//在指定结点执行后插方法

下面是封装好的函数

// 按位查找第i个结点
LNode *GetElem(LinkList L,int i){
	if(i==0)
		return L;	//若i等于0,则返回头结点
	if(i<1)
		return NULL;	//i无效,返回NULL

	int j=1;	//当前p指向第几个结点
	LNode *p=L->next;	//头结点的指针域赋值给p,p指向第一个结点
	while(p!=NULL && j<i){	//找到第i-1个结点
		p=p->next;
		j++;
		//j等于1时,p后移指向第2个结点
		//j等于2时,p后移指向第3个结点
		//...
		//j等于i-1时,p指向第i个结点
	}
	return p;
	
}
// 在指定结点后插新元素
bool InsertNextNode(LNode *p,ElemType e){
	if(p==NULL)
		return false;
	LNode *s = (LNode *)malloc(sizeof(LNode));
	if(s == NULL){
		return false;	//分配内存失败
	}
	s->data = e;
	//在怕结点后插s结点
	s->next = p->next;
	p->next = s;
	return true;
}
	

(4)前插法

由于单链表是单项的,给出结点p,只能顺藤摸瓜地找到它的后继结点。但是没有办法逆向找到它的前驱结点。

思路1:先从头到尾查找p的直接前驱,然后进行后插。代码同上面的后插法
思路2:将新结点s,后插到p后面,然后交换data,看起来不就是s前插到了p之前嘛。

思路2:代码

bool InsertPriorNode(LNode *p,Lnode *s){
	if(p==NULL || s==NULL)
		return false;
	s->next = p->next;	//s后插到p之后
	p->next = s;
	ElemType temp = p->data;	//交换数据域
	p->data = s->data;
	s->data = temp;

}

4.删除

ListDelete(&L,i,&e):删除操作。删除表L中第i个位置的元素,并用e返回删除元素的值。
王道视频里将头结点看作是第0个结点

方法1:遍历找到前驱结点,然后断链删除之。
e在形参中使用&,可以直接返回其值。

bool ListDelete(LinkList &L,int i,ElemType &e){
	if(i<1){
		return false;
	}
	int j=0;	//j表示当前p指向第几个结点,头结点为第0个
	LNode *p=L;	//L指向头结点
	while(p!=NULL && j<i-1){//查找第i-1个结点
		p=p->next;
		j++;
	}
	if(p==NULL){	//i值不合法
		return false;
	}
	if(p->next==NULL){//第i-1个结点之后已无其他结点
		return false;
	}
	LNode *q = p->next;	//q指向待删除结点
	e = q->data;
	p->next = q->next;
	free(q);
	return true;
}

方法2:偷天换日骚操作。删除某个结点p,交换p与后继结点的值,删除p的后继即可。
在这里插入图片描述

bool DeleteNode(LNode *p){
	if(p==NULL || p->next ==NULL){
		return false;	//p不合法或者p是最后一个结点,该方法失效
	}
	LNode *q = p->next;	//q指向p的后继结点
	p->data = p->next->data;	//p和后继交换数据信息
	p->next = q->next;	//删除q
	free(q);
	return true;
}

5.查找

不管是按位查找,还是按位查找,都需要从前往后遍历,所以时间复杂度都是O(n)

(1)按位查找

LNode *GetElem(LinkList L,int i){
	if(i<1) return NULL;	//i值不合法
	if(i == 0) return L;	//第0个结点为头结点
	
	int j= 1;
	LNode *p = L->next;	//p指向第1个结点
	while(p != NULL && j < i){
		p = p->next;
		j++;
	}
	return p;	//返回第i个结点的指针,若i大于表长,则返回NULL
}

(2)按值查找

注意:C语言比较struct类型的变量时,不能使用 == 比较相等。

可以封装一个判等方法。

LNode *LocateElem(LinkList L,ElmeType e){
	LNode *p = L->next;	//从第一个结点开始查找data域为e的结点
	while(p != NULL && p->data != e){
		p = p->next;
	}
	return p;	//找到后返回该结点的指针,找不到饭返回NULL
}

6.表长

表长 : 单链表中数据节点的个数。头结点不计入表长
因此带头结点与不带头结点的单链表,在计算表长时的代码逻辑有所不同。

三、双链表

在这里插入图片描述
1.定义

typedef struct DNode{
	ElemType data;
	struct DNode *prior,*next;
}DNode,*DlinkList;

多了一个指针域prior,

  • 双链表的按位查找与按值查找代码逻辑不变
  • 双链表的插入、删除操作与单链表的代码出现不同。需要增加对prior指针修改代码。
  • 其关键是保证修改的过程中不断链外,双链表还可以方便的找到前驱结点,因此插入、删除操作的时间复杂度仅为O(1)
  • 例如,在p前插(后插)一个s,删除结点p

2.双链表的插入操作
在这里插入图片描述
①②两步,必须在④之前,否则*p之后的后继结点的指针就会丢失,导致插入失败。

s->next = p->next;
if(p->next != NULL)
	p->next->prior = s;
s->prior = p;
p->next = s;

3.双链表的删除操作

在这里插入图片描述

p->next = q->next;
if(q->next != NULL)
	q->next->prior = p;
free(q);

四、循环链表

在这里插入图片描述

1.定义

typedef struct LNode{
	ElemType data;
	struct LNode *next;
}LNode,*LinkList;

2.初始化一个循环单链表

bool InitList(LinkList &L){
	L = (LNode *)malloc(sizeof(LNode));
	if(L == NULL) 
		return false;
	L->next = L;
	return true;
}

3.初始化一个循环双链表

bool InitDList(DLinkList &L){
	L = (LNode *)malloc(sizeof(LNode));
	if(L == NULL) 
		return false;
	L->next = L;
	L->prior = L;
	return true;
}

五、静态链表

单链表:各个结点在内存中星罗棋布、散落天涯。
静态链表:分配一整片连续的内存空间,各个结点集中安置。

在这里插入图片描述

1.定义

# define MaxSize 50
struct Node{
	ElemType data;	//存储数据元素
	int next;	//下个数组元素的下标
};
typedef struct Node SLinkList[MaxSize];
//可用 SLinkList 定义“一个长度为 MaxSize 的 Node 型数组”


SLinkList a;	//定义了一个数组,等同于 struct Node a[MaxSize];

next == -1 表示链表的结束。
插入、删除的操作只需要修改 “指针”,即next的数值
静态链表在不支持真实指针的高级语言里是一种非常巧妙的设计。

2.查找
必须从头往后遍历,所以时间复杂度为O(n)

3.性质
静态链表:用数组的方式实现的链表
优点:增、删 操作不需要大量移动元素
缺点:不能随机存取,只能从头结点开始依次往后查
找;容量固定不可变
适用场景:①不支持指针的低级语言;②数据元素数
量固定不变的场景(如操作系统的文件分配表FAT)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

_popo_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值