23王道——第二章习题

线性表的总结

文章目录

    • 顺序表还是链表好?
    • 2.3.7习题
      • 应用题1
      • 应用题2
      • 应用题3
      • 应用题4_1(其实是看错了,自己造了个新题)
      • 应用题4(正题来啦!!!)
      • 应用题5
      • 应用题6
      • 应用题7
      • 应用题8
      • 应用题10
      • 应用题11
      • 应用题12

顺序表还是链表好?

  1. 顺序表和链表的逻辑结构都是线性结构,都属于线性表

  2. 但二者的存储结构不同,顺序表采用顺序存储,需要预先分配大量的内存空间,但存储效率更高,链表采用链式存储,主要内存有空间就可以分配,操作灵活高效

  3. 由于采用不同的存储结构,基本操作的实现效率也不同,顺序表在按位查找元素时,时间复杂度为O(1),但插入删除元素时,平均要移动半个表长的元素,时间复杂度为O(n),采用折半查找的时间复杂度为O(log2n)

  4. 链表在按位查找元素时,时间复杂度为O(n),但在插入删除元素时,操作灵活,时间复杂度仅为O(1)

2.3.7习题

2.链式存储能够方便的表示各种逻辑关系

7.数组排序的最好时间为O(nlog2n)

18.在尾结点的单循环链表中,如果删掉最好一个结点,因为没有prior指针,所以需要从头遍历一遍

19.同理,不能忽略删掉最后一个元素,指针的移动问题

25.粗心。看反了顺序

应用题1

删除不带头结点的单链表为x的结点,要求递归

两种解法,一种我的,一种书上的,在这个题确实书上给的简单,我当时思考的太多了,反而捆住了自己
书:
每次递归只检测第一个结点,是就删掉,不是就L->next进入递归
就是检测以L->next为首结点的链表,下层循环L就是上一层的next

void Deletemin(LinkList& L,int x) {
	if (L == NULL)
		return;
	if (L->data == x) {//第一个结点为所找
		LNode* p = L;
		L = L->next;
		delete p;
		Deletemin(L,x);
	}
	else {
		Deletemin(L->next, x);
	}
}

我:
分为了两种条件,第一个if,如果第一个结点是所求,就删掉,然后L进入递归
第二个if,就是s指向L的第二个结点,如果是所求,就删掉,然后next进入递归
相当于我一次循环检测了前两个结点,然后书上每次检测一个???
应该是这样,不过确实书上的简单,易于实现思考

void Deletemin(LinkList& L,int x) {
	LNode* s = L;
	if (L == NULL)
		return;
	if (L->data == x) {//第一个结点为所找
		LNode* p = L;
		L = L->next;
		delete p;
		Deletemin(L,x);
	}
	if (s->next->data == x) {//不是链表中第一个结点
		LNode* m = s->next;//所找结点
		s->next = m->next;
		delete m;
		Deletemin(L->next , x);
	}
}

应用题2

2.带头结点的单链表,删除值为x的结点,并释放空间,不唯一

/*
1.是要找到等于x的结点
2.是要在链表中删除这个结点
3.是要delete这个结点

*/
#include
using namespace std;

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

void DeleteLNode_x(LinkList& L,int x) {
	LNode* p = L->next;
	LNode* s = L;//p的前驱结点
	while (p != NULL) {
		if (p->data == x) {
			LNode* m = p;
			s->next = p->next;
			s = p;
			p = p->next;
			delete m;
		}
		else {
			s = p;
			p = p->next;
		}
	}
}

int main() {
	LinkList L;
	//没有初始化
	int a[10] = { 1,2,3,4,5,6,6,7,6,6 };
	DeleteLNode_x(L, 6);

	return 0;
}

应用题3

带头结点,反向输出

一开始我是想用一个数组,正序存放,然后倒序输出
看了答案,提到了递归,先找到链表最后一个结点
if(L->next!=NULL) 递归传入L->next
到了最后一个结点的时候,就进行输出
但还要注意,因为是带头结点的,如果只有递归函数,因为L是指向头结点的,那么在第一层递归的时候就也会进行输出data
所以下面第一层调用时,不选用输出的选项,这样在第一层调用时,不会影响

void R_print(LinkList L) {
	if (L->next != NULL)
		R_print(L->next);
	if (L != NULL) 
		cout << L->data;
}

void R_printfirst(LinkList L) {
	if (L->next != NULL)
		R_print(L->next);//是为了不输出头结点
}

应用题4_1(其实是看错了,自己造了个新题)

原本应该是删除带头结点的最小值的结点,结果写成了找到最小值
自己还觉得写的不错,结果对不上号
原本的一会写,放下面

自己用递归实现的,还以为经过昨天的递归函数,自己有了长进
只要不空,每次就将当前结点的next传入递归,比较min,继续递归

void findmin(LinkList L, int& min) {
	if (L != NULL) {//如果第n个不空
		if (L->data < min)//小于就改变min
			min = L->data;
		findmin(L->next, min);//接着传入下一个结点
	}
}

void min(LinkList& L) {
	int min = L->next->data;//头结点的data
	findmin(L->next->next, min);//传入第二个结点
	cout << min;
}

应用题4(正题来啦!!!)

删除带头结点的单链表的最小值(唯一)

自己和书上其实有点不一样,怎么觉得我好想比它的好想
书上思想:
用p去遍历链表,pre指向p的前驱,minp保存最小的结点指针,minpre指向*minp结点的前驱,(看到这我就已经蒙了)
其实就是在p和pre顺序遍历的时候,找到min的就将minp和minpre移动过来,嗯,对,就是这样

我就是单纯用p->next去遍历链表,注意!!!不是p去遍历,因为用next去遍历的话,此时p指向的就是min的前驱,可以用另外的结点s保存下来,然后遍历结束,在将p=s->next,进行删除操作,delete即可

void Deletemin(LinkList& L) {
	int min = L->next->data;//头结点
	LNode* p = L->next;
	LNode* s = L;//这里只是初始化

	while (p->next != NULL) {
		if (p->next->data < min) {
			min = p->next->data;
			s = p;//s指向每次min的前驱
		}
		p = p->next;
	}
	p = s->next;//s是p的前驱,p所指是min
	s->next = p->next;//连接结点
	delete p;
}

应用题5

将头结点的单链表倒置,要求辅助空间复杂度为O(n)

我是借助了6题的思想,将链断开产生新链,扫描结点并插入到头结点后面,不过在调试时发现(敲重点
在进行到链表的最后一个结点时,因为需要一个s是p的后驱,保证不断链,此时s是NULL,会导致程序报错引起读写错误
所以当p是最后一个结点时,即s为NULL时,再另外连接一次

刚才看了答案,思路基本一致,不过他又声明了个结点指向L用于连接,(我觉得差别不大),切,我比他辅助空间还少一个,嘎嘎嘎

void down(LinkList& L) {
	LNode* p = L->next;//第一个结点
	LNode* s = p->next;//保证不断
	p->next = NULL;
	p = s;
	s = s->next;//s保证不断链
	while (p != NULL&&s !=NULL) {
		p->next = L->next;//头插
		L->next = p;
		p = s;
		s = s->next;
	}
	p->next = L->next;//最后一个结点单独插入
	L->next = p;
}

应用题6

把带头结点的单链表的变成递增有序

一开始的想法是,遍历链表,然后保存min值的结点,和第一个结点,遍历完事之后,将min和第一个结点的数值进行交换
然后递归调用
但是!!!在递归调用的时候一直有问题,无奈的我只能在主函数里面用了一个while循环才能正常求解

void Add(LinkList &L){
	LNode *p=L->next;//第一个结点
	LNode *s=L->next;
	LNode *min=s;//最小元素结点标记
	while(s!=NULL){
		if(s->data<min->data){
			min=s;
			s=s->next;
		}
		else
			s=s->next;//移动
	}
	int mm=min->data;//将min移动到第一位
	min->data=p->data;
	p->data=mm;
	//if(L->next!=NULL)
	//	Add(L->next);
	//原本是想这样递归调用的,不过一递归就是有问题
}

int main(){
	LinkList L;
	List_TailInsert(L);
	LNode *p=L;
	while(p->next!=NULL){
		Add(p);
		p=p->next;
	}
	printList(L);
	system("pause");//实验室电脑不加暂停就飞了
	return 0;
}

书上的,从前面断开产生新链表,每次扫描一个结点,对在新链表中找到要插入的位置,就是<就向后移动

void add(LinkList& L) {
	LNode* p = L->next;
	LNode* s = p->next;
	p->next = NULL;//断开,产生新链表
	p = s;
	while (p != NULL) {
		s = s->next;//s保证不会断链
		LNode* pre = L;//当pre
		while (pre->next != NULL && pre->next -> data < p->data)
			pre = pre->next;
		p->next = pre->next;//找到插入位置,就进行插入
		pre->next = p;
		p = s;
	}
}

应用题7

删除头结点的单链表中,data在一定范围内的结点

我是一个s,一个s前驱,用s去检测,为所求就用p去跳跃连接,然后delete
不过在第一次运行时,是因为没加else,导致s、p后移量变多
因为如果s是所求,当连接完成后,p还是s的前驱,并不用后移p,对吧,哈哈哈哈

void Delete_between(LinkList& L,int min,int max) {
	LNode* p = L;//第一个结点
	LNode* s = p->next;
	while (s != NULL) {
		LNode* r = s;//用于delete
		if (s->data > min&& s->data < max) {
			p->next = s->next;
			s = s->next;
			delete r;
		}
		else {
			s = s->next;
			p = p->next;
		}
	}
}

应用题8

找出两个单链表中的公共结点

就是每次L1中的一个结点,就遍历一下L2,调用了递归
但我还没看答案思路,马上图书馆要关门了,收拾东西溜了溜了
明早来看!!!!!

早上来了,发现自己好像没理解透题目,单链表的公共结点,不是那种简单data相同的结点,是同一块内存的结点,这个是我想的太简单了
单链表的next指针只有一个,所以当有一个公共结点之后,后面所有的结点一定都是相同的,也就是说,如果有公共结点,最后面的必然是公共结点,类似于Y形状。因为他不能中间有一段公共结点,然后又分开了,所以需要两个链表同步的向后扫,如果相等,则后面的都是公共结点(长度较长的需要先找到和断的一齐)齐头并进,对,就是这样,先齐头,再并进

void Findsame(LinkList L1, LinkList L2) {
	int m = L1->next->data;
	LNode* p = L2->next;
	while ( p!= NULL) {
		if (p->data == m)
			cout << m << " ";
		p = p->next;
	}
	if(L1->next!=NULL)
		Findsame(L1->next, L2);
}
LNode *Findsame(LinkList& L1, LinkList& L2) {
	int len1 = length(L1);
	int len2 = length(L2);
	LNode* longlen, *shortlen;
	int num = 0;
	if (len1 > len2) {
		longlen = L1->next;
		shortlen = L2->next;
		num = len1 - len2;
	}
	else {
		longlen = L2->next;
		shortlen = L1->next;
		num = len2 - len1;
	}
	while (num) {//齐头
		longlen = longlen->next;
		num--;
	}
	while (longlen != NULL) {//并进
		if (longlen==shortlen)
			return longlen;
		longlen = longlen->next;
		shortlen = shortlen->next;
	}
	return NULL;
}

应用题10

将一个单链表,按奇偶次序,拆成两个单链表

就直接遍历,单数就插入到第一个结点后面,双数就插入到第二个结点后面,记得定义一个新的头结点,然后链接

LinkList split(LinkList& L) {
	LNode *newList = new LNode;
	LNode* p = L->next;//奇数
	LNode* s = p->next;//偶数
	newList->next = s;
	LNode* r = s->next;
	int j = 3;//计数器
	while (r != NULL) {
		if (j % 2 == 1) {
			p->next = r;
			p = r;
			r = r->next;
			j++;
		}
		else {
			s->next = r;
			s = r;
			r = r->next;
			j++;
		}
	}
	p->next = NULL;
	s->next = NULL;
	return newList;
}

应用题11

有一个头结点的单链表{a1,b1,a2,b2…an,bn}
要求拆成{a1,a2…an}和{bn…b2,b1}

定义新头结点,然后遍历,一个尾插,一个头插,over
其中在尾插时,r的后面要有个结点,保证不断链,要不先连r的话,r->next就会丢啦

LinkList split_double(LinkList& L) {
	LNode* newList = new LNode;//新的头结点
	LNode* p = L->next;//a1
	LNode* s = p->next;//b1
	newList->next = s;
	int j = 3;//判断an还是bn
	LNode* r = s->next;//遍历结点
	while (r != NULL) {
		if (j % 2 == 1) {//an
			p->next = r;
			p = r;
			r = r->next;
			j++;
		}
		else {//bn,这里连接有问题,如果先连r,那就会断链
			LNode* k = r->next;//不断链
			r->next = newList->next;
			newList->next = r;
			r = k;
			j++;
		}
	}
	p->next = NULL;//断尾
	s->next = NULL;
	return newList;
}

应用题12

删除单链表(递增有序)中,数值重复的结点

就是保存前一个结点的data,去比较它后面的结点,相同就删除,不同就更新data为后面的结点数值,继续遍历
注意删除时,p结点的前驱s不用移动

void delete_same(LinkList& L) {
	LNode* p = L->next;
	LNode* s = L;//p的前驱,用于删除结点
	int num = p->data;
	p = p->next;
	s = s->next;
	while (p != NULL) {
		if (p->data == num) {
			LNode* r = p;//用于delete
			s->next = p->next;
			//s=s->next;这里不用移动,删除结点,s本身也不动
			p = p->next;
			delete r;
		}
		else {
			num = p->data;
			p = p->next;
			s = s->next;
		}
	}
}

你可能感兴趣的:(王道,c++,数据结构,链表)