线性表----链式表

定义

线性表的链式存储又称单链表,它是指通过任意的存储单元来存储线性表的数据。注意此时的数据在物理地址上不在连续,内存是动态分配的,而且数据是存放在结点中,结点组成链表,每个节点分为数据域和指针域,所以我们在定义的时候,数据域来存放数据,指针域存放后面结点的地址(因为地址不连续。必须有一个变量来知道下一个结点在哪里)
代码描述:

typedef int ElemType;
typedef struct LNode {
	ElemType data;
	struct LNode* Next;
}LNode,*LinkList;

特点

  • 可以解决顺序表需要大量连续存储空间的缺点
  • 单链表有指针域,我们只要存储数据,现在又存储了个指针,浪费了内存
  • 知道第i个位置,就知道后面所有的结点内容了,但不能知道前面的结点

基本操作

再说基本操作之前,我们先来了解头指针和头结点

  • 头指针:标识一个单链表
  • 头结点:第一个结点之前附加的一个结点,可有可无。头结点的数据域可以不设置任何信息,指针域指向第一个元素结点,就是数据域有存放我们要的数据的结点
  • 区别:头指针就是链表的名字,指向第一个结点,这个理解的时候,可以联想数组,数组名就是头指针。如果有头结点,假如数组名叫a,a[1]就是头结点,只是这个里面不存放数据,头指针指向头结点,头指针的值就是头结点的首地址,头结点的指针域的值是存放第一个数据的首地址

1、建立单链表
1.1头插法
假如现在链表只有一个数据是1,按照头插法在插入数据2,那么现在的数据是2,1,第一个数据是2,这就是头插法

/*
头插法建立单链表,将新的数据插入当前链表的表头
*/

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;
		s->next = L->next;
		L->next = s;
		scanf("%d", &x);
	}
	return L;
}

我们知道头结点L是第一个结点,里面不存放数据,所以我们在插入数据s时,只需将头结点L的指针域的值赋值给新插入数据S的指针,这样新插入的数据S就指向头指针L指向的结点,现在新插入的结点s和头结点L都指向了一个结点,相当于两个头结点了,但头结点只能有一个,所以我们的L还要指向S,始终保证L都是第一个节点,s就是第一个带有我们存放数据的结点

1.2 尾插法

/*
尾插法:
	在尾部插入数据
*/
LinkList List_TailInsert(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;
}

此时多了个r指针,r始终表示最后一个结点,它的值是最后一个结点的首地址,我们插入一个新结点s时,尾插法要把s放到数据最后,没有插入s前,r表示最后一个结点,所以让r的指针域指向s,r现在是倒数第二个结点,但r始终表示最后一个结点,所以要把r指向s

1.3 尾插法和头插法

  • 尾插法生成的链表结点的次序与输入数据的顺序相同,而头插法不一致
  • 每个结点插入的时间为0(1),链表表长为n,则时间复杂度为0(n)
  • 头插法的L始终表示头结点,尾插法的r始终要表示最后一个结点

2、按序号查找结点值
思路:从第一个节点出发,顺指针next依次往下找,当找到第i个结点结束,否则返回null

/*
	按序号查找结点值:
*/
LNode* GetElem(LinkList L, int i) {
	int j = 1;		//计数
	LNode* p = L->next;
	if (i <= 0)
		return NULL;
	while (p && j < i) {
		p = p->next;
		j++;
	}
	return p;
}

头结点为第一个结点,不存放值,所以不必要查找,直接j=1就行,为什么要p&&j<i呢?因为我们在创建我们的链表时,没有定义变量来记录长度,所以我们就不知道链表的长度,当j<i,但已经超过链表的长度时,我们就不能查找下去了,刚好最后一个结点的next是null,能判断有没有到达最后一个结点

3、按值查找
返回第一个等于我们要查找值的结点

/*
	按照值来查找
*/

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

4、插入结点
将数据e插入到第i个结点上,首先判断i的合法性

/*
插入到第i个位置
*/

bool Insert(LinkList& L, int i,ElemType e) {
	LNode* p = GetElem(L, i - 1);
	if (p != NULL) {
		LNode* s = (LNode*)malloc(sizeof(LNode));
		s->data = e;
		s->next = p->next;
		p->next = s;
		return true;
	}
	else
	{
		return false;
	}
	
}

首先查找第i-1位置结点,为什么不是i呢?因为插入的时候,相当于第i个位置后移一步,所以第i-1的next要指向新插入的i位置,新插入的i位置要指向旧的i位置结点,如果查找i,就没办法知道i-1位置。根据查找i-1位置来判断i的合法性。如果不合法返回false

5、删除操作
将单链表的第i个结点删除。
思路:先判断删除位置的合法性,然后查找第i-1个结点,删除第i位置

/*
删除操作
*/

bool Delete(LinkList& L, int i) {
	LNode* p = GetElem(L, i - 1);
	if (p != NULL) {
		LNode* q;
		q = p->next;
		p->next = q->next;
		free(q);
		return true;
	}
	else
	{
		return false;
	}
}
发布了168 篇原创文章 · 获赞 14 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_41683305/article/details/104347796