第三章 线性表的链式存储结构(单链表超详细讲解)

3.6.1 线性表链式存储结构定义

目录

3.6.1 线性表链式存储结构定义

3.6.2 单链表的存储结构

 3.6.3 单链表基本操作的实现

(一)单链表的初始化

(二)判断链表是否为空

(三)销毁单链表

(四)清空单链表

(五)求单链表的表长

(六)取单链表中的第i个元素的内容

(七)按值查找——根据指定数据获取该数据所在的位置(地址)

(八)按值查找--根据指定数据获取该数据位置序号

(九)在L中第i个元素之前插入数据元素e

(十)删除--删除第i个结点

 (十一)头插法

  (十二)尾插法

3.6.3 全部代码


定义:n个结点链结成一个链表,即为线性表(\large a_{1},\large a_{2},a_{3},...,a_{n})的链式存储结构。

数据域:存储数据元素信息; 指针域:存储直接后继位置的地址。


 结点:数据元素的存储映像,由数据域和指针域两部分组成。

链表:n个结点由指针链成的一个链表。


头指针、头结点和首元结点:

头指针:是指向链表中的第一个结点的指针

首元结点:是指链表中存储第一个数据元素 \large a_{1}的结点。

头结点:是在链表的首元结点之前附设的一个结点。

 空表:无头结点时,头指针为空时为空表;有头结点时,头结点的指针域为空时为空表。

3.6.2 单链表的存储结构

单链表:

带有头结点的单链表:

 

 空链表:

单链表的存储结构:

 3.6.3 单链表基本操作的实现

(一)单链表的初始化

Status lnitList_L(LinkList& L)
{
	L = (Lnode*)malloc(sizeof(Lnode));
	//两种初始化是一样的,都是指针类型
	//L = (LinkList)malloc(sizeof(Lnode));
	L->next = NULL;
	return OK;
}

(二)判断链表是否为空

算法思路:判断头结点指针域是否为空

int ListEmpty(LinkList L)
{
	if (L->next)
	{
		return 0;
	}
	else return 1;
}

(三)销毁单链表

 算法思路:从头指针开始,依次释放所有结点

Status DestroyList_L(LinkList &L)
{
	LinkList p;
	//Lnode *p;
	while (L)
	{
		p = L;//从头结点开始,L存放着头结点的地址
		L = L->next;//指向下一个结点
		free(p);//删除
	}
	return OK;
}

(四)清空单链表

算法思路:依次释放所有的结点,并将头指针域设置为空

Status ClearList(LinkList &L)
{
	Lnode* p, * q;//或者LinkList p, q;
	p = L->next;//头结点的next域存放着首元结点的地址
	while (p)
	{
		q = p->next;//q存放了要删除元素的下一个结点的地址
		free(p);
		p = q;
	}
	L->next = NULL;//头结点指针域为空
	return OK;
}

(五)求单链表的表长

算法思路:从首元结点开始,依次计数所有结点

Status ListLength_L(LinkList L)
{
	Lnode* p;//LinkList p
	p = L->next;//p指向第一个结点
	int count = 0;
	while (p)//遍历单链表,统计结点数
	{
		count++;
		p = p->next;
	
	}
	return count;
}

(六)取单链表中的第i个元素的内容

算法步骤:

1.从第1个结点(L->next)顺链扫描,用指针p指向当前扫描到的结点,p初值p=L->next。

2.j做计数器,累计当前扫描过的结点数,j初值为1;

3.当p指向扫描到的下一个结点时,计数器j加1;

4.当j==i时,p所指向的结点就是要找的第i个结点。

Status GetElem_L(LinkList L, int i, ElemType &e)//由变量e返回
{
	Lnode* p;
	int j = 0;
	p = L->next; j = 1;//初始化
	while (p && j < i)//向后扫描,直到p指向第i个元素或p为空
	{
		p = p->next;
		++j;
	}
	if (!p || j > i)//第i个元素不存在
		return ERROR;
	e = p->data;//	取第i个元素
	return OK;
}

(七)按值查找——根据指定数据获取该数据所在的位置(地址)

算法步骤:

1.从第一个结点起,依次和e相比较。
2.如果找到一个其值与e相等的数据元素,则返回其在链表中的“位置” 或地址;
3.如果查遍整个链表都没有找到其值和e相等的元素,则返回0或"NULL”

Lnode* LocateElem_L(LinkList L, ElemType e)
{
	Lnode* p;//LinkList p
	p = L->next;
	while (p && p->data != e)
	{
		p = p->next;
	}
	return p;//返回地址
}

(八)按值查找--根据指定数据获取该数据位置序号

int LocateElem_L1(LinkList L, ElemType e)
{
	Lnode* p; int j = 0;
	p = L->next; j = 1;
	while (p && p->data != e)
	{
		p = p->next;
		j++;
	}
	if (p)
		return j;
	else
		return 0;

}

(九)在L中第i个元素之前插入数据元素e

算法步骤:

 

Status Listlnsert_L(LinkList& L, int i, ElemType e)
{
	Lnode* p;
	Lnode* s;
	int j = 0;
	p = L;
	j = 0;
	while (p && j < i - 1)//寻找第i-1个结点,p指向i-1结点
	{
		p = p->next;
		++j;
	}
	if (!p || j > i - 1)//i大于表长+1或小于1,插入的位置非法
		return ERROR;
	s = (Lnode*)malloc(sizeof(Lnode));//或s=new LNode,生成新节点s
	s->data = e;//生成新结点s,将结点s的数据置为e
	s->next = p->next;//将保存的下一个结点的地址赋给新结点
	p->next = s;//把新结点的地址赋给前一个元素的指针域
}

(十)删除--删除第i个结点

算法步骤:

Status ListDelete_L(LinkList& L, int i, ElemType& e)
{
	Lnode* p;
	Lnode* q;
	int j = 0;
	while (p->next && j < i - 1)//寻找第i个结点,并令p指向其前驱
	{
		p = p->next;
		++j;
	}
	if (!(p->next) || j > i - 1)
		return ERROR;
	q = p->next;//临时保存被删除结点的地址以备释放
	p->next = p->next->next;//==q->next,指向被删除结点的下一个结点
	e = q->data;//保存删除结点的数据域
	free(q);//释放删除结点的空间
	return OK;

}

单链表的查找、插入、删除算法时间效率分析:

1.查找:

  • 因线性链表只能顺序存取,即在查找时要从头指针找起,查找的时间复杂度为O(n)。

2.插入和删除:

  • 因线性链表不需要移动元素,只要修改指针,一般情况下时间复杂度为O(1)。
  • 但是,如果要在单链表中进行前插或删除操作,由于要从头查找前驱结点,所耗时间复杂度为O(n)。

 (十一)头插法

1.从一个空表开始,重复读入数据;
2.生成新结点,将读入数据存放到新结点的数据域中

3.从最后一个结点开始,依次将各结点插入到链表的前端

void CreateList_H(LinkList &L, int n)
{
	Lnode* p;
	L = (Lnode*)malloc(sizeof(Lnode));//	L = new Lnode;
	L->next = NULL;//先建立一个代头结点的单链表
	int i = 0;
	for (i = n; i > 0; --i)
	{
		p = (Lnode*)malloc(sizeof(Lnode));//生成一个新结点
		scanf("%d", &p->data);//输入元素值,根据要元素的类型来
		p->next = L->next;//插入到表头
		L->next = p;

	}
}

  (十二)尾插法

 

void CreateList_R(LinkList& L, int n)
{
	Lnode* p;
	Lnode* r;
	L = (Lnode*)malloc(sizeof(Lnode));//	L = new Lnode;
	L->next = NULL;//先建立一个代头结点的单链表
	r = L;//尾指针r指向头结点
	int i = 0;
	for (i = 0; i < n; ++i)
	{
		p = (Lnode*)malloc(sizeof(Lnode));//生成一个新结点
		scanf("%d", &p->data);//输入元素值,根据要元素的类型来
		r->next = p;//插入到表尾
		r = p;//r指向新的尾结点
	}
}

3.6.3 全部代码

#include<stdio.h>
#include <malloc.h>
#define OK 1
#define ERROR 0
typedef int Status;
typedef int ElemType;//ElemType取决于我们要存储元素的类型
typedef struct Lnode {
	ElemType data;//结点的数据域
	struct Lnode* next;//结点的指针域
}Lnode,*LinkList;//LinkList为指向结构体Lnode的指针类型
//定义链表L:LinkList L==Lnode* L;
//定义结点指针p:LNode *p==LinkList p;

//单链表的初始化
Status lnitList_L(LinkList &L)
{
	L = (Lnode*)malloc(sizeof(Lnode));
	//两种初始化是一样的,都是指针类型
	L = (LinkList)malloc(sizeof(Lnode));
	L->next = NULL;
	return OK;
}

//判断链表是否为空
int ListEmpty(LinkList L)
{
	if (L->next)
	{
		return 0;
	}
	else return 1;
}
//单链表的销毁,销毁后不存在
Status DestroyList_L(LinkList &L)
{
	LinkList p;
	//Lnode *p;
	while (L)
	{
		p = L;//从头结点开始,L存放着头结点的地址
		L = L->next;//指向下一个结点
		free(p);//删除
	}
	return OK;
}
//清空单链表(头指针和头结点都存在)
Status ClearList(LinkList &L)
{
	Lnode* p, * q;//或者LinkList p, q;
	p = L->next;//头结点的next域存放着首元结点的地址
	while (p)
	{
		q = p->next;//q存放了要删除元素的下一个结点的地址
		free(p);
		p = q;
	}
	L->next = NULL;//头结点指针域为空
	return OK;
}
//求单链表的表长
Status ListLength_L(LinkList L)
{
	Lnode* p;//LinkList p
	p = L->next;//p指向第一个结点
	int count = 0;
	while (p)//遍历单链表,统计结点数
	{
		count++;
		p = p->next;
	
	}
	return count;
}


//取单链表中第i个元素的内容
Status GetElem_L(LinkList L, int i, ElemType &e)//由变量e返回
{
	Lnode* p;
	int j = 0;
	p = L->next; j = 1;//初始化
	while (p && j < i)//向后扫描,直到p指向第i个元素或p为空
	{
		p = p->next;
		++j;
	}
	if (!p || j > i)//第i个元素不存在
		return ERROR;
	e = p->data;//	取第i个元素
	return OK;
}

//按值查找--根据指定数据获取该数据所在的位置(地址)
Lnode* LocateElem_L(LinkList L, ElemType e)
{
	Lnode* p;//LinkList p
	p = L->next;
	while (p && p->data != e)
	{
		p = p->next;
	}
	return p;//返回地址
}
//按值查找--根据指定数据获取该数据位置序号
int LocateElem_L1(LinkList L, ElemType e)
{
	Lnode* p; int j = 0;
	p = L->next; j = 1;
	while (p && p->data != e)
	{
		p = p->next;
		j++;
	}
	if (p)
		return j;
	else
		return 0;

}

//在L中第i个元素之前插入数据元素e
Status Listlnsert_L(LinkList& L, int i, ElemType e)
{
	Lnode* p;
	Lnode* s;
	int j = 0;
	p = L;
	j = 0;
	while (p && j < i - 1)//寻找第i-1个结点,p指向i-1结点
	{
		p = p->next;
		++j;
	}
	if (!p || j > i - 1)//i大于表长+1或小于1,插入的位置非法
		return ERROR;
	s = (Lnode*)malloc(sizeof(Lnode));//或s=new LNode,生成新节点s
	s->data = e;//生成新结点s,将结点s的数据置为e
	s->next = p->next;//将保存的下一个结点的地址赋给新结点
	p->next = s;//把新结点的地址赋给前一个元素的指针域
}

//删除--删除第i个结点
Status ListDelete_L(LinkList& L, int i, ElemType& e)
{
	Lnode* p;
	Lnode* q;
	p = L->next;
	int j = 0;
	while (p->next && j < i - 1)//寻找第i个结点,并令p指向其前驱
	{
		p = p->next;
		++j;
	}
	if (!(p->next) || j > i - 1)
		return ERROR;
	q = p->next;//临时保存被删除结点的地址以备释放
	p->next = p->next->next;//==q->next,指向被删除结点的下一个结点
	e = q->data;//保存删除结点的数据域
	free(q);//释放删除结点的空间
	return OK;

}
//打印单链表
void printLinkList(LinkList L)
{
	Lnode* p;
	p = L->next;
	while (p != NULL)
	{
		printf("%d ", p->data);
		
		p = p->next;

	}
	printf("\n");
	
}

//头插法
void CreateList_H(LinkList& L, int n)
{
	Lnode* p;
	L = (Lnode*)malloc(sizeof(Lnode));//	L = new Lnode;
	L->next = NULL;//先建立一个代头结点的单链表
	int i = 0;
	for (i = n; i > 0; --i)
	{
		p = (Lnode*)malloc(sizeof(Lnode));//生成一个新结点
		scanf("%d", &p->data);//输入元素值,根据要元素的类型来
		p->next = L->next;//插入到表头
		L->next = p;

	}
}

//尾插法
void CreateList_R(LinkList& L, int n)
{
	Lnode* p;
	Lnode* r;
	L = (Lnode*)malloc(sizeof(Lnode));//	L = new Lnode;
	L->next = NULL;//先建立一个代头结点的单链表
	r = L;//尾指针r指向头结点
	int i = 0;
	for (i = 0; i < n; ++i)
	{
		p = (Lnode*)malloc(sizeof(Lnode));//生成一个新结点
		scanf("%d", &p->data);//输入元素值,根据要元素的类型来
		p->next = NULL;
		r->next = p;//插入到表尾
		r = p;//r指向新的尾结点
	}
}
int main()
{
	ElemType e;

	LinkList L;

	lnitList_L(L);
	CreateList_R(L, 6);
	printLinkList(L);
	
	printf("%d\n", ListLength_L(L));
	ListDelete_L(L, 2, e);
	printf("e的值为 % d\n", e);
	printf("%d\n", ListLength_L(L));
	Listlnsert_L(L, 2, 10);
	printLinkList(L);


}

  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值