408计算机综合数据结构笔记--线性表(2)链表

408计算机综合数据结构笔记–线性表(2)链表

分类

单链表,双链表,循环链表,静态链表

单链表

单链表的定义

每个节点除了存放数据外还要存放指向下一个节点的指针
代码定义

struct LNode{ //定义单链表节点类型
	ElemType data;//定义存放数据类型
	struct *LNode next;//存放下一个节点位置的指针
}LNode,*LinkList;
//新增一个节点,申请该节点所需空间,并用指针p指向该节点
LNode*p=(LNode*)malloc(sizeof(LNode))

Notes:
1)定义节点p时,LNodep和LinkList p意思一样。都是定义了一个struct LNode类型的指针。但是程序中所表达的含义中,LNode强调这是一个节点,LinkList强调这是一个单链表。

单链表的初始化

不带头节点

bool InitList(LinkList &L){
	L=NULL;
	return true;
}
void text(){
	LinkList L;
	//初始化一个空表
	InitList(L);
}

带头结点

bool InitList(LinkList &L){
	L=(LNode*)malloc(sizeof(LNode*));
	if(L==NULL)  //内存不足,创建头节点失败
		return false;
	L->next=NULL;  //头节点之后还没有节点
	return true;
}

void text(){
	LinkList L;
	//初始化一个空表
	InitList(L);
}

头节点只存放指针不存放数据
二者区别:
对第一个数据节点和后续数据节点的处理的代码逻辑不同;
对空表和非空表的处理的代码逻辑不同。
总之,带头节点的写代码更方便,所以一般都带头节点

单链表的操作–插入

单链表按位序插入

ListInsert(&L,i,e):在表L中第i个位置插入元素e
带头节点

bool ListInsert(LinkList &L,int i,ElemType e){
	if(i<1)
		return false;
	LNode *p;  //指向当前扫描到的节点
	int j=0; 	//当前p指向的是第几个节点
	p=L;	//L指向头节点,头节点是第0个节点(不存数据)
	while(p!=NULL&&j<i-1){ //循环找到第i-1个节点
		p=p->next;
		j++;
	}
	if(p==NULL)	//i值不合法
		return false;
	LNode *s = (LNode*)malloc(sizeof(LNode));
	s->data=e;
	s->next=p->next; 
	p->next=s;   //将节点s连到p之后
	return true;//插入成功
}

不带头节点

bool ListInsert(LinkList &L,int i,ElemType e){
	if(i<1)
		return false;
	if(i==1){//插入第1个节点的操作与其它节点不同
		LNode*s=(LNode*)malloc(sizeof(LNode));
		s->data=e;
		s->next=L;
		L=s; //头指针指向新节点
		return true;
	}
	LNode *p;  //指向当前扫描到的节点
	int j=1; 	//当前p指向的是第几个节点
	p=L;	//L指向第1个节点
	while(p!=NULL&&j<i-1){ //循环找到第i-1个节点
		p=p->next;
		j++;
	}
	if(p==NULL)	//i值不合法
		return false;
	LNode *s = (LNode*)malloc(sizeof(LNode));
	s->data=e;
	s->next=p->next; 
	p->next=s;   //将节点s连到p之后
	return true;//插入成功
}

单链表操作–后插

在p节点之后插入元素e

bool InsertNextNode(LNode*p,ElemType e){
	if(p==NULL)
		return false;
	LNode *s=(LNode*)malloc(siezof(LNode));
	if(s==NULL)
		return false;
	s->data=e;
	s->next=p->next;
	p->next=s;//将节点s连到p之后
	return true;
}

单链表操作–前插

在节点p之前插入元素e

bool InsertPriorNode(LNode*p,ElemType e){
	if(p==NULL)
		return false;
	LNode *s = (LNode*)malloc(sizeof(LNode*));
	if(s==NULL)
		return false;
	s->next=p->next;
	p->next=s;//新节点s连接到p之后
	s->data=p-<data;//p的元素给s
	p->data=e;//用e覆盖p中的元素
	return true;
}

单链表的操作–删除

按位序删除

ListDelete( &L,i,&e):删除表L中第i个节点,并用e返回删除数据的值
即:找到第i-1个节点,并将其指向i+1个节点,并释放第i个节点。
带头结点

bool ListDelete(LinkList &L,int i,ElemType &e){
	if(i<1)
		return false;
	LNode*p;
	j=0;
	p=L;
	while(p!==NULL&&j<i-1){
		p->next;
		j++;
	}
	if(p==NULL)
		return false;
	if(p->next==NULL)
		return false;
	LNode *q=p->next;//令q指向被删除的节点
	e=q->data;//用e返回被删除节点的值
	p->next=q->next;//将*q节点从链中断开,即i-1个节点指向i+1个节点
	free(q);
	return true;
}

删除指定节点

bool DeleteNode(LNode *p){
	if(p==NULL)
		return false
	LNode *q=p->data;//令q指向*p的后继节点
	p->data=p->next->data;//和后继节点交换数据域
	p->next=q->next;//将*q从链中断开
	free(q);
	return true;
}

单链表的操作–查找

按位查找

返回第i个元素
带头结点

LNode*GetElem(LinkList L,int i){
	if(i<0)
		return NULL;
	LNode *p;
	int j;
	p=L;
	while(p!==NULL&&j<i){
		p=p->next;
		j++;
	}
	return p;

}

按值查找

LNode *LocadeElem(LinkList L,ElemType e){
	LNode *p;
	p=L->next;
	while(p!==NULL&&p->data!=e)
			p=p->next;
	return p;
}

单链表的操作–求表长

int Length(LinkList L){
	int len=0;
	LNode *p=L;
	while(p->next!=NULL){
		p=p->next;
		len++;
	}
	return len;	
}

单链表的操作–建立

尾插法

在p节点之后插入元素e
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保存元素e
	s->next=p->next;
	p->next=s;//将节点s连到P后面
	return true;
}

尾插法建立示例

LinkList List_Taillnsert(LinkList &L){
	int x;
	L=(LinkList)malloc(sizeof(LNode));//建立头节点
	LNode *s,*r=L;  //r为表为指针;
	scanf("%d",&x);//输入节点的值
	while(x!=9999){ //设置的一个特殊数,即输入9999表示结束
	s =(LNode*)malloc(sizeof(LNode));
	s->data=x;
	r->next=s;
	r=s;//指向新的表尾节点
	scanf("%d",&x);
	}
	r->next=NULL//尾节点指针为空
	return L;
}

头插法建立示例

LinkList List_Headlnsert(LinkList &L){
	LNode*s;
	int x;
	L=(LinkList)malloc(sizeof(LNode));//建立头节点
	L->next=NULL;//初始化为空链表  //如果没有这句?
	scanf("%d",&x);//输入节点的值
	while(x!=9999){
		s=(LNode*)malloc(sizeof(LNode));//创建新节点
		s->data=x;
		s->next=L->next;
		L->next=s;
		scanf("%d",&x);
	}
	return L;
}

如果没有L->next=NULL;,最终最后一个节点有可能指向一片未知的区域。
头插法应用:链表的逆置

双链表

双链表的初始化

带头结点

typedef struct DNode{
	ElemType data;
	struct DNode *Prior,*next;
}DNode,*DLinklist;
//初始化双链表
bool InitDLinkList(DLinklist &L){
	L=(DNode*)malloc(sizeof(DNode)); //分配一个头节点
	if(L==NULL) //内存不足,分配失败
		return false;
	L->prior=NULL;//头结点的prior永远指向NULL
	L->next=NULL;//头结点之后暂时还没有节点
	return true;
}

void testDLinkList(){
	DLinkList L;
	InitDLinkList(L);
}

双链表的插入

//p节点之后插入s节点
bool InsertNextDNode(DNode*p,DNode*s){
	if(p==NULL||s==NULL)
		return false;
	s->next=p->next;
	if(p->next!=NULL)//如果p后面还有后继节点
		p->next->prior=s;
	s-prior=p;
	p->next=s;
}

如果要进行前插操作只需要找到该节点的前驱并进行后插操作即可

双链表的删除

//删除p结点的后继结点
bool DeleteNextDNode(DNode){
	if(p==NULL) return false;
	DNode*q=p->next;
	if(q==NULL) return false;
	p->next=q->next;
	if(q->next!=NULL) //如果q有后继节点
		q->next->prior=p;
	free(q);
	return true;
	}

双链表的遍历

//后向遍历
while(p!=NULL){
	p->next;
}
//前向遍历
while(p!=NULL){
	p=p->prior;
}
//前向遍历跳过头结点
while(p->prior=NULL){
	p=p->prior;
}

双链表不可随机存取,按值查找,按位查找,都可通过遍历来实现。
时间复杂度为O(n);

循环链表

即让单链表或者双链表的尾结点指向头结点。

循环单链表

初始化

typedef struct LNode{
	ELemType data;
	struct LNode *next;
}LNode,*LinkList;
//初始化一个循环单链表
bool InitList(LinkList &L){
	L=(LNode*)malloc(sizeof(LNode));
	if(L==NULL)
		return false;
	L->next=L;
	return true;
}

循环双链表

初始化

typedef struct DNode{
	ElemType data;
	struct DNode *prior,*next;
}DNode,*DLinkList;
bool InitDLinkList(DLinklist &L){
	L=(DNode*)malloc(sizeof(DNode));
	if(L==NULL)	return false;
	L->prior=L;
	L->next =L;
	return ture;
}

静态链表

分配一整片连续的空间,各个结点集中安置。
包含:数组元素和下一个节点的数组下标(游标)。
数组下标为0的节点为头结点,头结点下一个结点为头结点游标所指示的数组下标所在的节点。其中最后一个结点游标的值设置为-1

定义一个静态链表示例

#denfine MaxSize 10
struct Node{
	ElemType data;
	int next;
};
void testSLinkList(){
	struct Node a[MaxSize];
}
//或者:
struct Node{
	ElemType data;
	int next;
}SLinkList[MaxSize];

顺序表与链表的比较

逻辑结构

都是线性结构,都属于线性表。

存储结构

顺序表链式表
顺序存储链式存储
优点:支持随机存取 ,存储密度高优点:离散的小空间分配方便,改变容量方便
缺点:大片连续空间分配不方便,改变容量不方便缺点:不可随机存取,存储密度低

基本操作

顺序表链式表
销毁:1)修改length=0;静态数组,系统自动收回空间;动态数组,需要手动free销毁:依次删除(free)各个结点
插入/删除:要将元素后移/前移;时间复杂度O(n),时间开销主要来自于移动元素插入删除:只需要修改指针即可;时间复杂度O(n),时间开销主要来自于查找目标元素;但是查找元素的时间代价要比移动元素的时间代价低
查找:按位查找O(1);按值查找O(n),若表内元素有序O( log ⁡ 2 n \log_2n log2n)查找:按值查找O(n);按位查找O(n)

比较总结

表长难以预估,经常要增加删除元素-----链表;
表长可以预估,查询操作更多-----顺序表

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值