系统掌握数据结构3线性表C++实现


(本文以及接下来的每一篇都是这样的结构,先说逻辑结构,然后初步认识物理结构,然后实现代码,最后结合代码重新认识物理结构的优缺点,本章的代码与习题代码都打包成了文件: 线性表及习题代码

1.逻辑结构

  1. 线性表:零个或多个相同数据类型的数据元素的有限序列。

(元素之间是有顺序的。第一个元素无前驱,最后一个元素无后继,其他元素都有且只有一个前驱和一个后继。)

  1. 空表:线性表元素的个数n定义为线性表的长度,当n=0时,称为空表
  2. 在较为复杂的线性表中,数据元素可以由多个数据项组成。

(线性表是一种逻辑结构,它的物理结构可以用顺序表或链表实现,不要混淆线性表和顺序表)

2.存储结构

(存储结构结合第三节代码实现来看)

2.1顺序存储
  1. 线性表的顺序存储结构,指的是用一段地址连续的存储单元一次存储线性表的数据元素。

(线性表顺序存储时逻辑顺序和物理顺序相同)

  1. 顺序表存储结构是一种随机存取的存储结构,因为每一种元素所占的字节是相同的,很容易对任意一个位置的元素进行操作。
  2. 基本结构:类似于数组。
2.2链式存储
  1. 链式存储:用一组任意的存储单元存储线性表的数据元素,对于数据元素ai除了存储了数据元素信息,还存储了后继位置(也就是指针)。

  2. 基本结构:链表由n个节点组成,每一个节点有一个数据域存储元素的值,还有一个指针域,存储该元素后继的地址也就是指针。指向第一个节点位置的指针我们称为头指针,最后一个节点的指针指向NULL。(初级的单链表结构)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-k79MlZgS-1646902693167)(线性表.assets/p14.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kDIA6azR-1646902693168)(线性表.assets/p15.png)]

  1. 头节点结构:在列表的第一个节点前我们增加一个头节点,头节点的数据域存储链表的元素个数或者不存储任何信息,指针域存储第一个节点的位置,此时的头指针指向头节点。这样的结构更加规范,至于为什么大家在代码中体会,以后的学习中链表通常都会有头节点。

3.线性表的实现

3.1 顺序存储结构实现

3.1.1 静态分配

#define MaxSize 50
typedef int ElemType;	
//ElemType可以是任何数据类型,为举例方便此处设为int
typedef struct {
	ElemType data[MaxSize];//存储元素的数组
	int length;//记录元素个数
}SqList;

静态分配方便大家了解线性表的顺序存储结构实现,但是当我们的元素个数超过MaxSize时会发生数据溢出。

3.1.2 动态分配

#define InitSize 50
#define IncreaseSize 10 //容量不足时一次增加的容量
typedef int ElemType;

typedef struct {
	ElemType *data;
	int MaxSize, length;//length记录元素个数,MaxSize是当前线性表容量。length<=MaxSize
}SqList;

动态分配在容量不足时会扩容,所以接下来的线性表操作都是以动态分配的顺序存储结构为基础。

3.2顺序存储操作实现

(前言:关于SqList &L,表示引用的意思,L的类型是SqList, 如果在函数里L的值被改变,主函数里L的值也会变的)

(SqList L,传参,L的类型是SqList, L的值的改变并不会影响主函数中的L,但是L->data的值改变了,原函数L->data的值也会变的)

(SqList *L,这里的L是一个地址,改变L的值并不会影响主函数的值,但是L->data的值改变了,原函数L->data的值也会变的,其实就是对参数的改变不会影响主函数,但是对一些地址存储的值的改变是会影响主函数的。大家自己实验理解好方便我们实现操作)

#define InitSize 50
#define IncreaseSize 10 
#define OK 1			//为了方便函数编写,表示操作成功
typedef int Status;		//Status是函数返回值类型,可以是任何类型,此处用int
typedef int ElemType;

typedef struct {
	ElemType *data;
	int MaxSize, length;
}SqList;

3.2.1 初始化线性表

/*
获得一个线性表
*/
Status InitSqList(SqList& L) {
	L.data = (ElemType*)malloc(sizeof(ElemType)*InitSize); //分配初始内存
	if (!L.data)					 //若分配失败则结束程序
		return ERROR;
	L.MaxSize = InitSize;			//容量赋值
	L.length = 0;					//元素赋值
	return OK;
}
int main() {
	SqList L ;
	int x = InitSqList(L);
	cout << x << endl;				//打印结果为1
}

3.2.2 插入元素

Status ListInsert(SqList& L, int i, ElemType e) {
	if (i < 1 || i > L.length + 1)
		return ERROR;				//插入元素的位置要合法,i=1时插入在最前面,i=length+1时插入在末尾
	if (L.length >= L.MaxSize) {	//容量不足
		//重新分配内存
		L.data = (ElemType*)realloc(L.data, sizeof(ElemType) * (L.MaxSize + IncreaseSize));
		if (!L.data) return ERROR;
		L.MaxSize = L.MaxSize + IncreaseSize;
	}
	for (int j = L.length; j >= i; j++) {//第i个位置及之后的位置的元素都向后挪动一个元素的位置
		L.data[j] = L.data[j - 1];
	}
	L.data[i - 1] = e;				//第i个位置的索引是i-1
	L.length++;
	return OK;
}

为了验证我们写的对不对,我们写一个打印线性表的函数

Status ListPrint(SqList L) {
	for (int i = 0; i < L.length; i++) {
		cout << L.data[i] << "";
		cout << "\t" << "";
	}
	cout << "" << endl;
	return OK;
}

然后调用函数

int main() {
	SqList L ;
	int x = InitSqList(L);
	for (int i = 0; i < 55; i++) {
		ListInsert(L, i + 1, i + 1);
	}
	ListPrint(L);//打印出1 2 3...55 说明函数写对了
}

3.2.2删除操作

/*
删除i位置元素
*/
Status ListDelete(SqList& L, int i) {

	if (i < 1 || i > L.length) //位置不合法
		return ERROR;
	
	for (int j = i; j < L.length; j++) {//第i+1位置和之后的都向前挪动一个元素的位置
		L.data[j - 1] = L.data[j];
	}
	L.length--;
	return OK;
}

同时验证一下

int main() {
	SqList L ;
	int x = InitSqList(L);
	for (int i = 0; i < 55; i++) {
		ListInsert(L, i + 1, i + 1);
	}
	ListPrint(L);
	ListDelete(L,30);//删除第30个位置的元素
	ListPrint(L);//打印结果1 2 ...29 31 ... 55
}

3.3.2 按值查找

int GetElem(SqList L, ElemType e) {
	for (int i = 0; i < L.length; i++) {
		if (L.data[i] == e) {
			return i;
		}
	}
	return -1;//表示没有找到
}

检查一下

int main() {
	SqList L ;
	int x = InitSqList(L);
	for (int i = 0; i < 55; i++) {
		ListInsert(L, i + 1, i + 1);
	}
	ListPrint(L);
	ListDelete(L,30);
	ListPrint(L);
	cout << GetElem(L, 30) << endl; //打印-1
	cout << GetElem(L, 29) << endl; //打印28 说明写对了
}

顺序存储的其他操作都很简单了,接下来大家可以自行试着实现能想到的所有操作。

3.3链式存储结构实现(单链表)

(前言:本小节LinkList L是一个LNode* 类型,是地址,建立链表函数中我们需要改变L的值,所以应该是LinkList &L)

typedef struct LNode {
	ElemType data;
	struct LNode* next;
}LNode,* LinkList;			//LNode是节点,LinkList是头指针。
//相当于 typedef int* LinkList 此时LinkList是指向int数据的指针,上面只不过把int换成LNode
3.4链式存储操作实现(单链表)

3.4.1 头插法建立单链表:就是添加元素时添加在链表头。

/*
头插法建立单链表
*/
Status List_HeadInsert(LinkList &L){
	LNode* s=NULL;
	int e=9999;
	L = (LNode*)malloc(sizeof(LNode));//创建头节点,头指针L指向头节点。
	if (!L) return ERROR;
	L->next = NULL;
	cin >> e ;
	while (e != 9999) {
		s = (LNode*)malloc(sizeof(LNode));//s是新的节点
		if (!s) return ERROR;
		s->data = e;

		s->next = L->next;//s节点指向之前的节点
		L->next = s;	  //s始终插入到L后面
		cin >> e;
	}
	return OK;
}

同样的,为了验证我们写一段打印线性表的代码

/*
打印链表
*/
Status ListPrint(LinkList L) {
	LinkList p = L->next;				//获得头节点
	while (p!= NULL) {
		cout << p->data ;
		cout << "\t";
		p = p->next;
	}
	cout << "\t" << endl;
	return OK;
}
int main() {
	LinkList L;
	List_HeadInsert(L);
	ListPrint(L);
}

打印结果如下图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2cHMcAaS-1646902693169)(线性表.assets/p17.png)]

3.4.2 尾插法建立单链表:添加元素时添加在链表尾

/*
尾插法建立单链表
*/
Status List_TailInsert(LinkList& L) {
	LNode* s = NULL;
	LNode* r = NULL;
	int e = 9999;
	L = (LNode*)malloc(sizeof(LNode));
	if (!L) return ERROR;
	L->data = NULL;
	r = L;				//尾插法需要用到r来辅助
	cin >> e;
	while (e != 9999) {
		s = (LNode*)malloc(sizeof(LNode));//s为新的节点
		s->data = e;

		s->next = NULL;
		r->next = s;	
		r = s;			//r始终为链表最后一个节点
		cin >> e;
	}
	return OK;
}
int main() {
	LinkList L;
	//List_HeadInsert(L);
	List_TailInsert(L);
	ListPrint(L);
}

打印结果如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-t6PAhB5B-1646902693169)(线性表.assets/p18.png)]

3.4.3 按位置查找节点(按位置查找结点同理,大家可以自己试一下)

/*
获得第i个位置节点的地址
*/
LNode* GetElme(LinkList L, int i) {
	LNode *p = L->next;
	if (i < 1) return ERROR;
	i--;
	while (i > 0 && p!= NULL) {
		p = p->next;
		i--;
	}
	return p;
}
int main() {
	LinkList L;
	//List_HeadInsert(L);
	List_TailInsert(L);
	ListPrint(L);
	if(GetElme(L, 1)!=NULL)
		cout << GetElme(L, 1)->data << endl;
	if(GetElme(L, 5)==NULL)
		cout << ERROR << endl;
}

打印结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2T1NnHBv-1646902693170)(线性表.assets/p19.png)]

3.4.4 在第i个位置插入元素

/*
在第i个位置插入元素e
*/
Status LinkListInsert(LinkList L, int i, ElemType e) {
	LNode* r = NULL;
	LNode* s = NULL;
	if (i == 1) r = L;
	else r = GetElme(L, i-1); //获得第i个位置的前一个节点

	if (r == NULL)
		return ERROR;		//若前一个节点为NULL则i不合法
	s = (LNode*)malloc(sizeof(LNode));
	if (!s) return ERROR;
	/*在节点i-1后插入节点*/
	s->data = e;
	s->next = r->next;
	r->next = s;

	return OK;
}
int main() {

	LinkList L;
	//List_HeadInsert(L);
	List_TailInsert(L);
	ListPrint(L);
	ElemType e = 9999;
	cin >> e;
	LinkListInsert(L, 3, e);
	ListPrint(L);
}

打印结果如下:其他比如在某个节点前插入等方法大家可以自己试一下。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YpSjlzgG-1646902693171)(线性表.assets/p20.png)]

3.4.5 删除节点

/*
删除节点i
*/
Status LinkListDelete(LinkList L, int i) {
	LNode* r = NULL;
	LNode* s = NULL;
	if (i == 1)
		r = L;
	else r = GetElme(L, i - 1);

	if (r == NULL) return ERROR;
	if (r->next == NULL) return ERROR;

	s = r->next;	
	r->next = s->next;
	free(s);
	return OK;
}
int main() {

	LinkList L;
	//List_HeadInsert(L);
	List_TailInsert(L);
	ListPrint(L);
	
	LinkListDelete(L, 3);
	ListPrint(L);

}

打印结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kI1CSwiV-1646902693171)(线性表.assets/p21.png)]

3.5 线性表实现-静态链表

3.5.1 静态链表的物理结构

typedef struct{
	ElemType data;
	int cur;
}SLinkList[MaxSize];

使用数组来实现链表的功能,数组元素有一个数据域和一个指针域,指针域的值并不是地址,而是下一个数组元素的索引。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Y0fA7hOj-1646902693172)(线性表.assets/p22.png)]

实际上并没有链表方便,但是有一些高级语言并不支持指针,这是一种实现链表的巧妙的方法,大家学习这种思想就好了。大家如果学习过操作系统,也会很熟悉这种思想。关于它的操作代码就略过了,不重要。

3.6线性表实现-循环链表

3.6.1循环链表的物理结构:我们将链表最后一个节点的next由NULL改为头节点,那么整个链表就变成了一个圈。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tFSXnpk4-1646902693172)(线性表.assets/p23-1646820579626.png)]

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

发现了吧,和普通单链表一模一样。其实就是在创建链表的时候有区别,我们把头插法和尾插法代码写一下如下:

3.7.2 头插法建立循环链表:和普通单链表只差了注释的那一行,其他部分一模一样

Status RLinkList_HeadInsert(RLinkList& L) {
	int e = 9999;
	LNode* s = NULL;
	L = (LNode*)malloc(sizeof(LNode));
	if (!L) return ERROR;
	L->next =L;		//没有元素时头节点自己指向自己而非NULL 
	cin >> e;
	while (e != 9999) {
		s = (LNode*)malloc(sizeof(LNode));
		if (!s) return ERROR;
		s->data = e;

		s->next = L->next;
		L->next = s;
		cin >> e;
	}
	return OK;
}

3.7.3 尾插法建立循环链表:同样只差一行

Status RLinkList_TailInsert(RLinkList& L) {
	int e = 9999;
	LNode* s = NULL;
	LNode* r = NULL;
	L = (LNode*)malloc(sizeof(LNode));
	if (!L) return ERROR;
	L->next = L;	//没有元素时头节点自己指向自己而非NULL 
	r = L;
	cin >> e;
	while (e != 9999) {
		s = (LNode*)malloc(sizeof(LNode));
		if (!s) return ERROR;
		s->data = e;

		s->next = r->next;
		r->next = s;
		r = s;
		cin >> e;
	
	}
	return OK;
}

为了验证同样要写个打印双链表的。

Status RLinkListPrint(RLinkList L) {
	LNode* p = L->next;
	while (p != L) { //循环结束条件变了
		cout << p->data;
		cout << "\t";
		p = p->next;
	}
	cout << "\t" << endl;
	return OK;
}

我们来试一下:

int main() {
	RLinkList L1 = NULL, L2 = NULL;
	cout << "头插法:" << endl;
	RLinkList_HeadInsert(L1);
	RLinkListPrint(L1);
	cout << "尾插法:" << endl;
	RLinkList_TailInsert(L2);
	RLinkListPrint(L2);
}

打印结果如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-p4AszeHz-1646902693172)(线性表.assets/p24.png)]

其他的插入删除等方法和单链表一模一样,甚至把LinkList改为RLinkList就可以直接用了,此处略过。

3.7线性表实现-双链表

3.7.1 双链表的物理结构:单链表是指单向,我们只能从链表头查单链表尾,如果我们想从链表尾查到链表头那再加一条链子吧!
在这里插入图片描述

typedef struct LNode {
	ElemType data;
	struct LNode* next;//后继指针
	struct LNode* prior;//前驱指针
}LNode, * DLinkList;

3.7.2 头插法 尾插法 建立双链表:

Status DLinkList_HeadInsert(DLinkList &L){
	int e = 9999;
	LNode* s = NULL;
	L = (LNode*)malloc(sizeof(LNode));
	if (!L) return ERROR;
	L->next = NULL;
	L->prior = NULL; //多一个“链子” 比单链表多的步骤
	cin >> e;
	while (e != 9999) {
		s = (LNode*)malloc(sizeof(LNode));
		s->data = e;

		L->next->prior = s; //后继节点的前驱指向s节点 比单链表多的步骤
		s->next = L->next;	//s的后继指向其后继节点
		s->prior = L;		//s的前驱指向L节点		  比单链表多的步骤
		L->next = s;		//L的后继指向s节点
		cin >> e;
	}
	return OK;
}
Status DLinkList_TailInser(DLinkList& L) {
	int e = 9999;
	LNode* s = NULL;
	LNode* r = NULL;
	L = (LNode*)malloc(sizeof(LNode));
	if (!L) return ERROR;
	L->next = NULL;
	L->prior = NULL; //多一个“链子” 比单链表多的步骤
	r = L;
	cin >> e;
	while (e != 9999) {
		s = (LNode*)malloc(sizeof(LNode));
		s->data = e;

		L->next->prior = s; //后继节点的前驱指向s节点 比单链表多的步骤
		s->next = L->next;	//s的后继指向其后继节点
		s->prior = L;		//s的前驱指向L节点		  比单链表多的步骤
		L->next = s;		//L的后继指向s节点

		r = s;
		cin >> e;
	}
	return OK;
}

写一个打印函数。这个打印函数可以正反双向打印哦!

Status ListPrint(DLinkList L, int reverse = 0) {
	DLinkList p = L->next;				//获得头节点
	if (reverse == 1) {					//反向打印
		while (p->next != NULL)
			p = p->next;
		while (p != L) {
			cout << p->data;
			cout << "\t";
			p = p->prior;

		}
	}//if
	else {
		while (p != NULL) {
			cout << p->data;
			cout << "\t";
			p = p->next;
		}
	}//elese

	cout << "\t" << endl;
	return OK;
}

然后我们来验证一下:

int main() {
	DLinkList L1 = NULL, L2 = NULL;
	cout << "头插法建立链表L1" << endl;
	DLinkList_HeadInsert(L1);
	cout << "正向打印L1" << endl;
	ListPrint(L1);//默认是0 正向打印
	cout << "逆向打印L1" << endl;
	ListPrint(L1, 1);
	cout << "尾插法建立链表L2" << endl;
	DLinkList_TailInsert(L2);
	cout << "正向打印L2" << endl;
	ListPrint(L2);//默认是0 正向打印
	cout << "逆向打印L2" << endl;
	ListPrint(L2, 1);
}

打印结果如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3a9yOWgn-1646902693173)(线性表.assets/p26.png)]

3.7.3 按位置取值操作

LNode* GetElem(DLinkList L, int i, int reverse = 0) {
	if (i < 1) return NULL;
	LNode*  p = L->next;
	i--;
	if (reverse != 1) {
		while (i > 0 && p != NULL) {
			p = p->next;
			i--;
		}//while
	}//if
	else {
		while (p->next != NULL && p != NULL)//寻找最后一个节点
			p = p->next;
		while (i > 0 && p != L) {
			p = p->prior;
			i--;
		}
	}
	return p;
}
int main() {
	DLinkList L1 = NULL;
	DLinkList_TailInsert(L1);
	cout << GetElem(L1, 3)->data << endl;
	cout << GetElem(L1, 3,1)->data << endl;

}

打印结果如图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HYlSNT9y-1646902693173)(线性表.assets/p27.png)]

3.7.4 插入操作

单链表在插入节点时都要先找到该节点的前驱,然后向后插入。双链表直接找到i插入到它前面就好了。

Status DLinkListInser(DLinkList L, int i, ElemType e) {
	LNode* l = GetElem(L, i);
	if (!l) return ERROR;
	LNode* s = (LNode*)malloc(sizeof(LNode));
	s->data = e;

	s->next = l;
	s->prior = l->prior;
	l->prior->next = s;
	l->prior = s;

	return OK;
}

然后我们验证一下:

int main() {
	DLinkList L1 = NULL, L2 = NULL;
	DLinkList_TailInsert(L1);
	ListPrint(L1);
	DLinkListInser(L1, 3, 520);
	ListPrint(L1);

}

打印结果如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fD8ea2H0-1646902693174)(线性表.assets/p28.png)]

3.7.5 删除元素

Status DLinkListDelete(DLinkList L, int i) {
	LNode* l = GetElem(L, i);
	if (!l) return ERROR;

	l->prior->next = l->next;
	if (l->next != NULL)
		l->next->prior = l;
	return OK;
}

我们验证一下:

int main() {
	DLinkList L1 = NULL, L2 = NULL;
	DLinkList_TailInsert(L1);
	DLinkListDelete(L1, 3);
	ListPrint(L1);
}

打印结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hdAdpiHO-1646902693174)(线性表.assets/p29.pn

3.8循环双链表

3.8.1 物理结构 头节点的前驱指向尾节点,尾节点的后继指向头节点。

typedef struct LNode {
	ElemType data;
	struct LNode* next;
	struct LNode* prior;
}LNode, * RDLinkList;

代码是一样的,不过它叫RDLinkList,至于为什么不是DRLinkList,因为我喜欢呀!而头插法和尾插法也和双链表只差了两行:

Status RDLinkList_HeadInsert(RDLinkList& L) {
	int e = 9999;
	LNode* s = NULL;
	L = (LNode*)malloc(sizeof(LNode));
	if (!L) return ERROR;
	L->next = L;	//与双向链表只差这一行
	L->prior = L;   //与双向链表只差这一行
	cin >> e;
	while (e != 9999) {
		s = (LNode*)malloc(sizeof(LNode));
		s->data = e;

		if (L->next != NULL) //插入第一个节点时没有后继节点
			L->next->prior = s;

		s->next = L->next;	//s的后继指向其后继节点
		s->prior = L;		//s的前驱指向L节点		  比单链表多的步骤
		L->next = s;		//L的后继指向s节点

		cin >> e;

	}
	return OK;
}

Status DLinkList_TailInsert(RDLinkList& L) {
	int e = 9999;
	LNode* s = NULL;
	LNode* r = NULL;
	L = (LNode*)malloc(sizeof(LNode));
	if (!L) return ERROR;
	L->next = L;//与双向链表只差这一行
	L->prior = L; //与双向链表只差这一行
	r = L;
	cin >> e;
	while (e != 9999) {
		s = (LNode*)malloc(sizeof(LNode));
		s->data = e;

		//尾插法后面无节点			
		s->next = r->next;	//s的后继指向其后继节点
		s->prior = r;		//s的前驱指向L节点		  比单链表多的步骤
		r->next = s;		//L的后继指向s节点

		r = s;
		cin >> e;
	}
	return OK;
}

我们来写一个不一样的打印函数,他会正向打印或逆向打印n个节点。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-L3hHiyLJ-1646902693175)(线性表.assets/p30.png)]

其他操作与双向链表一模一样。代码实现就到此结束了。

4. 总结与分析

4.1顺序表与链表的比较

存取方式:顺序表可以随机存取,链表只能从头顺序存取元素。

逻辑结构与物理结构:采用顺序存储时逻辑上相邻的元素物理上也是相邻的,采用链式存储时逻辑上相邻的元素物理上不一定相邻。

查找、删除与插入操作:若按位置查找,顺序表时间复杂度=O(1),链表=O(n)。

空间分配:顺序表静态分配时容易发生溢出,分配过大也可能浪费空间,不好掌握分配值大小。顺序表动态分配时,每次重新分配都需要移动元素位置,因为原来连续的空间未必满足重新分配空间的大小,操作效率低。链表的空间分配非常简单、灵活。

4.2 选择哪种物理结构?

基于存储的考虑:难以估计线性表的存储规模的时候,应选择链表。

基于运算的考虑:若查找频繁,顺序表是一个好的选择。若插入、删除频繁,链表是一个好的选择。

基于环境的考虑:有些语音并没有指针,容不得我们选择链表。

总结来说,稳定的线性表选择顺序存储,频繁插入、删除的线性表选择链式存储。

5.习题

(习题答案在:线性表及习题代码

习题1.

已知一个带有头节点的单链表,假设该链表只给出了头指针list,在不改变链表的前提下。请设计一个尽可能高校的算法,查找链表中导数第k个位置的节点,k为整数。若查找成功算法输出该节点data域的值并返回1,否则只返回0。

习题2.

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-24aYrCHT-1646902693175)(线性表.assets/p31.png)]

这个题目如果用普通的选择排序思想很容易但是时间复杂第为O(n^2)。所以要想出一个时间复杂度更小的。提醒:和习题1思想一样。

习题3.

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2LAbu77q-1646902693175)(线性表.assets/p32.png)]

提示:普通方法都要O(n^2).所以想一个O(n)算法吧,可以考虑用空间换时间。

习题4.

链表原地逆置,对于带头结点的链表L,设计一个空间复杂度为O(1),也就是不借助辅助空间,将单链表中元素逆置。

习题5.

线性表L=(a1,a2,…,an),我们设计一个空间复杂度为O(1)的算法,将线性表L变为L=(a1,an,a2,an-1,…)。

我们的答案时间复杂度未O(n),不要超过了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值