数据结构与算法碎片积累(一)

背景:非科班梳理基础知识ing

知识点:

20201028
1、啥叫程序设计?
答:程序设计=数据结构+算法

2、啥叫数据结构?
答:数据元素之间的关系集合。

3、算法设计体现例子:
从1加到100之和

//方法一:
int add_1()
{
    
    
	int i, sum = 0, n = 100;//执行1次
	for (i = 1; i <= n; i++)//执行n+1次
	{
    
    
		sum += i;//执行了n次
	}
	return sum;
}//运行次数100次

//方法二:
int add_2()
{
    
    
	int i, sum = 0, n = 100;//执行了1次
	sum = (1 + n) * n / 2;//执行了1次
	return sum;
}//运行次数1次

从上面看,这里把执行得到sum所执行的代码行数简单理解为称为时间复杂度,方法一时间复杂度2n+2;方法二的时间复杂度为2,显然当n=100时,方法二的时间复杂度更小,更加优秀。这也感性体会了什么叫算法设计。

4、啥叫算法?
答:指令方式(敲得一行一行的代码,注意要有技巧和方式的,比如上面的例子比对),告诉计算机怎么解决特定问题。

5、算法有啥特征?
输入:0或多个输入
输出:至少有一个输出
有穷性:可接受时间内,自动结束运行(不出现死循环)
确定性:每行代码有明确的含义,或者说,条件不变,输入不变的话,输出一定不变
可行性:每个步骤都是可行的(可行意思就是,每个步骤或每行代码执行的次数有限)

20201030
6、算法设计时有啥要求呀?
答:
1)正确性:指的是算法至少应该具有输入、输出和加工处理无歧义性,正确反映问题的需求、能够得到问题答案。(基本段位水平,由低到高:无语法错误、合法输入由满足要求的输出、非法输入有相应的说明,刁难测试输入有满足要求输出)

2)可读性:有相应的注释,便人便己理解

3)健壮性:非法输入时,不会出现程序异常或崩溃

4)时间效率搞和存储量低:运行尽量快(时间复杂度小),占用内存尽量小(空间复杂度小),当然两者要权衡。

7、什么叫算法的效率?影响的因素有哪些?
答:一般指的是该算法的执行时间。影响的因素有,算法的设计好坏,输入量的规模。(输入量,简单理解为输入数据量,如果很大的话,自然执行时间长了)

8、算法时间复杂度的官方说辞咋样的?
答:时间复杂度指的是,在进行算法分析时,语句总的执行次数T(n)是关于问题规模n的函数,进而分析T(n)随n的变化情况并确定T(n)的数量级。
一般情况下,随着输入规模n的增大,T(n)增长最慢的算法为最优算法。

9、算法时间复杂度的简单理解是怎么样的?
答:执行次数==时间。

10、怎么表示算法时间复杂度?
答:利用大写O()来表示,简称大O表示法。

11、一般怎么算法时间复杂度?
答:数一数从输入到输出过程中,所有语句执行的次数,就表示时间复杂度。

12、如何分析一个算法的时间复杂度?或者理解为怎么计算O(?)。
答:借助n取无穷大思想考虑,保留n最高阶项,把常数部分置为1,其他部分就是时间复杂度了。如,4n ^ 3 +2n ^ 2+4n+10 ,那么时间复杂度表示为O(n^3).

20201031
13、算法上,常见的时间复杂度有哪些?排序怎样?
答:O(1)<O(logn)<O(n)<O(nlogn)<O(n ^ 2)<O(n ^ 3)<O(2 ^ n)<O(n!)<O(n^n)

14、啥叫做算法的空间复杂度?
答:指的是算法所需的存储空间。

15、一般情况说的复杂度指的是啥?
答:时间复杂度。

16、算法时间复杂度和空间复杂度,谁比较更应该追求?
答:视情况而定的,并没有谁优秀的说法。比如,单片机上面就追求空间复杂度更小为优先了。当然,一般情况下,希望执行时间更短更好,就希望时间复杂度小一些。编程时,也可以使用大空间来换取少时间开销的。

17、啥叫做线性表?
答:由零个或多个数据元素组成的有序序列。重点突出,有顺序,个数有限。当线性表的长度为0时,称为空表。

18、啥叫抽象数据类型(abstract data type,ADT)?
答:指的是一个数学模型及定义在该模型的一组操作(数据类型+相关操作)。举个粟子,游戏角色位置xyz,我们定义一个point的抽象数据类型表示xyz的组合坐标,进而表示角色位置。

19、线性表的基本操作有哪些?
答:
InitList(*L):初始化操作,建立一个空得线性表L
ListEmpty(L):判断线性表是否为空表,若线性表为空,返回true,否则返回false。
ClearList(*L):将线性表清空。
GetElem(L,i,*e):将线性表L中得第i个位置元素值返回给e。
LocateElem(L,e):在线性表L中查找与给定值e相等得元素,如果查找成功,返回该元素;否则,返回0表示失败。
ListInsert(*L,i,e):在线性表L中第i个位置插入新元素e。
ListDelete(*L,i,*e):删除线性表L中第i个位置元素。*e作用时返回删除的数据。

简单理解,初判空,定查插删,指针返回。

20、线性表的物理存储结构形式有哪些?
答:顺序存储结构和链式存储结构。

21、线性表的顺序存储结构指的是啥?
答:指的是用一段地址连续的存储单元依次存储线性表的数据元素。物理上的存储方式,这里的物理上简单点理解硬盘这类,在内存中找到一个初始位置,然后通过占位的形式,把一定的内存空间占了,然后把相同数据类型依次放到这块空间中。

22、线性表的顺序存储结构封装(线性表对一个数组进行封装,多了当前长度属性)需要的三个属性是啥?
答:
1)存储空间的起始位置
2)线性表的最大存储容量(数组定义大小所决定的)
3)线性表的当前长度

23、如何计算线性表中的元素地址?
答:假设已知a1的位置,每个元素字节大小为c,那么求ai位置可以表示为Loc(ai)=Loc(a1)+(i-1)*c

24、线性表的第i位置元素怎么获取?
答:直接返回数组(线性表=数组+长度)下标i-1即可。

20201101
25、线性表的插入操作思路怎么样?
答:
1)插入的位置不合理,会提示错误
2)线性表的长度大于或等于数组(线性表=数组+长度)容量,提示异常或动态或动态增加数组容量
3)从最后一个元素开始后移,后移遍历到第i个位置
4)将插入的元素放到第i个位置上
5)线性表的长度+1
ex:

Status ListInsert(SqList* L, int i, ElemType *e)
{
    
    
	int k;
	
	if (L->length == MaxSize)//MaxSize表示链表最大数组容量,假设宏定义了
	{
    
    
		return ERROR;//顺序线性表已经满,提示异常
	}
	if (i<1 || i>L->length + 1)
	{
    
    
		return ERROR;//当i不在范围内,提示异常
	}
	if (i <= L->length)//若输入数据位置不在表尾
	{
    
    
		//将要插入的位置后数据元素向后移动一位
		for (k = L->length - 1; k >= i - 1; k--)
		{
    
    
			L->data[k + 1] = L->data[k];
		}
	}
	L->data[i - 1] = e;//将新元素插入
	L->length++;

	return OK;
}

26、线性表的删除思路是啥?
答:
1)删除位置不合理,提示错误
2)返回删除的位置元素数据
3)从删除元素位置开始遍历,依次将元素往前移动一个位置
4)线性表长度-1
ex:

Status ListInsert(SqList* L, int i, ElemType *e)
{
    
    
	int k;
	
	if (L->length == 0)
	{
    
    
		return ERROR;//顺序线性表已经空了,提示报错
	}
	if (i<1 || i>L->length)
	{
    
    
		return ERROR;//当i不在范围内,提示异常
	}
	*e = L->data[i-1];//这里是保存将要删除的值保存到指针变量*e中,用于返回
	if (i < L->length)//若输入数据位置不在表尾
	{
    
    
		//将要插入的位置后数据元素向前移动一位
		for (k = i; k <L->length; k++)
		{
    
    
			L->data[k-1] = L->data[k];
		}
	}
	L->length++;
	return OK;
}

27、线性表的插入和删除的时间复杂度特征是啥?
答:
1)最好情况是:插入和删除操作刚好要求在最后一个位置操作,因为不需要移动任何元素,所以时间复杂度为O(1);
2)最坏情况是:如果插入和删除的位置都是第一个元素,意味着要移动所有元素向后或者向前,这时候时间复杂度为O(n);
3)平均时间复杂度为O(n)

评价:线性表的顺序存储结构,在后面保存数据或者读取数据时候,时间复杂度为O(1),插入或者删除操作的时间复杂度为O(n)。那么,线性表比较适合元素个数比较稳定,不经常插入和删除数据的应用

28、线性表的顺序存储结构的优缺点是啥?
答:
1)优点:元素间的逻辑关系不需要额外增加存储空间来保存,可以快速存(后继保存)取(任意位置)的数据。
2)缺点:插入或删除操作需要移动大量元素;线性表的长度变化较大时,难以确定存储空间的容量;容易导致空间的“碎片”(电脑上面的磁盘清理功能产生的原因之一)。

29、线性表链式存储结构特点是啥?
答:
1)存储单元由数据域和指针域(下一个存储单元的地址)组成
2)存储单元保存在内存未占用的任意位置
3)存储单元称为结点
4)链表中,第一个结点存储位置叫做头指针,最后一个结点为空(null)

30、链表中的头指针和头结点关系是啥?
答:
1)头结点的数据域一般不存储任何信息的;
2)头指针指的是链表指向第一个结点的指针,若链表有头结点,则此时,头指针就是指向头结点的指针;
3)头指针具有标识作用,所以头指针一般用链表的名字赋值(数组名可以退化一个指针常量,指向第一个元素);
4)无论链表是否为空,头指针都不为空;
5)头指针是链表的必要元素;头结点不一定是链表的必要元素
6)头结点设定是为了操作的便利性(有了头结点,对于第一个元素结点的前插入或删除操作跟其他结点一致),其数据域一般没有意义,但也可以用来保存链表的长度;头结点并不是链表的必要元素。

31、链表的数据域和指针域怎么理解?
答:假设p是指向线性表的第i个元素ai的指针,则该结点ai的数据域,可以使用p->data的值来表示;而p->next表示ai的指针域,指向的是第i+1元素ai+1。这里也可以这么认为,p->next->data存放的是ai+1的数据域。

32、单链表读取第i个数据算法思路是啥?
答:基本思路是,从第一个结点开始挨个找:
1)声明一个结点p,指向第一个结点,初始化j从1开始;
2)当j<i时,遍历链表,让p的指针向后移动,不断指向下一个结点,j+1;
3)若到链表末尾p为空,说明第i个元素不存在;
4)否则查找成功,返回p的数据

ex:

Status GetElem(LinkList L, int i, ElemType* e)
{
    
    
	int j;
	LinkList p;//声明链表指针

	p = L->next;//头指针就是和链表名称一样的,这里初始化我们定义的一个指针
	j = 1;

	while (p && j < i)//当p没有指向null( 0)时且遍历的次数次数还未超过i时则进行
	{
    
    
		p = p->next;
		++j;
	}
	if (!p || j > i)//指向null或者找的位置是个负数就报错了
	{
    
    
		return ERROR;
	}
	*e = p->data;
	
	return OK;
}//c代码,默认有了宏定义的

33、遍历单链表时,为啥选择while,而不选择for?
答:由于单链表的结构中没有定义表长,所以不知道循环多少次,因此就不方便使用for来控制循环。这里面用到一个思想是,工作指针后移,属于很多算法常用的技术。

34、单链表里,如何在ai和ai+1元素之间插入一个元素s?
答:假设p指向的是结点ai,那么有:
s->next=p->next;
p->next=s;
注意,上述的顺序一定不能调换。

35、单链表第i个数据插入结点,算法思路是啥?
答:
1)声明一节点p指向链表头结点,初始化j从1开始;
2)当j<1时,就遍历链表,让p的指针向后移动,不断指向下一个结点,j累加1;
3)若到链表末尾p为空(指向null了),则说明第i个元素不存在;
4)否则,查找成功,在系统中生成一个空结点s;
5)将数据元素e赋值给s->data;
6)单链表的插入上述34的标准语句;
7)返回成功

ex:

Status ListInsert(LinkList* L, ElemType e)
{
    
    
	int j;
	LinkList p;

	p = *L;
	j = 1;

	while (p && j < i)//让p找到i的位置
	{
    
    
		p = p->next;
		j++;
	}

	if (!p || j > i)
	{
    
    
		return ERROR;//找不到结点位置返回报错
	}

	a = (LinkList)malloc(sizeof(Node));//开辟新结点,先开辟大小,然后强行转为链表形式
	a->data = e;//将e的值赋给s新结点数值域

	a->next = p->next;//将待插入点的后结点位置赋值给新结点a,或者理解为结点a指针指向待插入点的后结点
	p->next = a;//待插入点指针指域指向新结点a

	return OK;
}

36、单链表怎么删除第i个结点q的?
答:思路,让其前结点的指针指向其后结点即可:
假设待删除结点q的前结点为p,那么有,
q=p->next;
p->next=q->next;

37、单链表的第i个数据元素删除结点算法思路是啥?
答:
1)声明一结点p指向链表第一个结点,初始化j=1;
2)当j<1时,就遍历链表,让p指针后移,j累加1;
3)若到链表末尾,p为空,则说明第i个元素不存在;
4)否则,查找成功,假设待删除的结点又叫q,那么将p->next赋值给q;
5)再进行,p->nxet=q->next;
6)将q结点的数据赋值给e,作为返回;
7)释放q结点。

ex:

Status ListInsert(LinkList* L, ElemType *e)
{
    
    
	int j;
	LinkList p, q;//q时用来保存找到的元素

	p = *L;
	j = 1;

	while (p->next && j < i)//让p找到i的位置
	{
    
    
		p = p->next;
		j++;
	}
	
	if (!(p->next) || j > i)//找不到结点位置返回报错
	{
    
    
		return ERROR;
	}

	q = p->next;//找到了第i个要删除的位置了,并赋值给q,表示q就是要删除元素
	p->next = q->next;//待删的前结点指针指向待删的后结点

	*e = q->data;
	free(q);

	return OK;
}

38、单链表插入和删除的时间复杂度是多少?
答:O(n),这里取最糟情况来表示的。

39、关于线性表,对比顺序存储和链表存储的插入和删除效率是怎样?
答:假设从第i个位置开始,连续插入10个元素:
1)对于顺序存储结构意味着,每一次插入都得移动n-i个位置,所以每次的时间复杂度都是O(n);
2)而单链表,只需要在第一次时,找到第i个位置的指针,此时为时间复杂度为O(n),接下来的通过赋值移动指针即可实现插入,时间复杂度均为O(1);
3)两者对比可见,对于插入和删除数据越频繁的操作,单链表的效率优势更明显。

20201102
40、创建单链表是一个动态生成过程,那整表创建的算法思路是怎么样的?
答:
1)声明一结点p和计算器变量i;
2)初始化一个空链表L;
3)让L的头结点的指针指向NULL,即建立一个带头结点的单链表;
4)循环实现后继结点的赋值和插入

41、动态创建单链表的方式有?
答:头插法、尾插法。

42、头插法创建单链表的算法怎么实现的?
答:头插法,从一个空表开始,生成新结点,读取的数据存放到新结点的数据域中,然后将新结点插入到当前的链表的表头上,直到结束为止。

简单点理解,就是不断将新添加的结点不断放到表头位置上。
——先将新结点的next指向头结点的next
——然后让表头的next指向新插入的结点

ex:

void CreateListHead(LinkList* L, int n)
{
    
    
	LinkList p;//声明一个结点
	int i;

	srand(time(0));//初始化随机数种子

	*L = (LinkList)malloc(sizeof(Node));
	(*L)->next = NULL;//初始化空链表,并且*L为表头

	for (int i = 0; i < n; i++)
	{
    
    
		p = (LinkList)malloc(sizeof(Node));//生成新结点
		p->data = rand() % 100 + 1;//随机生成1-100内数值,新结点数值域赋值
		p->next =* L)->next;//新结点链接到老结点上面
		(*L)->next = p;//让表头指向到新结点
	}
}

43、尾插法创建链表的算法是怎么实现的?
答:
把新结点插在最后。实现方式是,添加一个标志点用于标志当前链表的最后结点位置,然后循环,不断插入新结点。假设待插入的新结点为p,标志点为r,那么,
r->next=p;
r=p;
关键循环顺序就可以实现尾插创建链表。
ex:

//尾插法创建链表
void CreateListHead(LinkList* L, int n)
{
    
    
	LinkList p,r;//声明两个结点,p表示新结点,r表示标记点,指向链表最后
	int i;

	srand(time(0));//初始化随机数种子

	*L = (LinkList)malloc(sizeof(Node));
	 r=*L;//r指向空表的表头
	 

	for (int i = 0; i < n; i++)
	{
    
    
		p = (LinkList)malloc(sizeof(Node));//生成新结点
		p->data = rand() % 100 + 1;//随机生成1-100内数值,新结点数值域赋值
		r->next=p;//新结点链接到老结点上面
		r = p;//将r移动到当前的最后结点
	}
}

44、单链表整表删除算法思路是啥?
答:
1)声明结点p和q;
2)将第一个结点赋值给p,下一个结点赋值给q;
3)循环执行释放p和将q赋值给p的操作
ex:

Status ClearList(LinkList* L)
{
    
    
	LinkList p, q;
	p = (*L);
	while (p)
	{
    
    
		q = p->next;//q记录下一节点位置
		free(p);//释放当前节点p(数据域和指针域)
		p = q;//将p移动到q记录的节点位置
	}
	(*L)->next = NULL;

	return OK;
}

注意:这里删除链表操作时,循环体不能够直接写成
free( p );
p=p->next;
的,这是因为,free(p)释放了p的数据域和指针域,是找不到其下一个结点在哪的,所以一定需要q来提前保存p下一结点位置才行。

45、单链表结构和顺序存储结构的优缺点怎么对比?
答:
1)存储方式:
——顺序存储结构用一段连续的存储单元依次存储线性表的数据元素
——单链表采用链式存储结构,用一组任意的存储单元存放线性表的元素(不连续也OK意思)

2)时间性能(时间复杂度):
——查找上:顺序存储结构O(1),单链表O(n);
——插入和删除上:顺序存储结构O(n),单链表O(1)。

3)空间性能:
——顺序存储结构需要预分配存储空间,分大了,容易造成空间浪费,分小了,容易溢出;
——单链表不需要分配预分配存储空间,只要有位置就可以分配,元素个数也不受限制

综合考虑,有:
——若线性表需要频繁查找,很少插入和删除操作时,应该采用顺序存储结构
——若需要频繁插入和删除操作,应采用单链表操作
——元素所需空间不明确或者变动大时,应采用单链表结构;否则,采用顺序结构存储效率更高
——总之,根据实际情况选择对应的线性表结构。

20201110
46、啥叫做静态链表?
答:使用数组描述的链表称为静态链表。当然,这种描述方法叫做游标实现法。

#define MAXSIZE 1000
typedef struct
{
    
    
	ElemType data;//数据
	int cur;      //游标(Cursor)
}Component,StaticLinkList[MAXSIZE];

47、静态链表有啥特征?
答:
1)结构体组成,游标、数据、下标;
2)存储特点:下标最大的游标保存的是存储数据区间的第一个元素;下标为0的游标保存的是备用链表的第一个位置的下标;数据存储区,最后一个元素的游标是0;每个点链接通过 游标-下标 方式实现的。
如1000个结点的静态链表:
在这里插入图片描述
48、静态链表注意事项有哪些?
答:(可以结合上表看)
1)链表中数组第一个和最后一个元素做特殊处理,数据区不存放数据;
2)通常将未使用的数组元素称为备用链表;
3)数组的第一个元素,即下标为0的元素游标存放备用链表的第一个结点的下标;
4)数组的最后一个元素,即下标最大的结点的游标存放第一个有数值的元素下标,相当于单链表的头结点的作用。

49、静态链表的插入操作怎么样?
答:寻找备用链表的第一个结点;使其下标数值保存道需要插入点的游标,而该结点的游标,修改为插入点的后点的下标;修改数组第一个元素的游标,变更道备用链表的第一个空结点的下标。
ex:

//静态链表的插入操作
//首先获取空闲分量的下标,下面为关键操作的说明
int Malloc_SLL(StaticLinkList space)
{
    
    
	int i = space[0].cur;//获取的是备用链表(空表)的第一位置
	if (space[0].cur)//判断备用链表不为空
		space[0].cur = space[i].cur;//把它的现空表的第一个元素的下一个
			 //分量用作备用;因为现在的空表第一个元素准备用于插入数据了
	return i;//返回的是空表的第一个元素位置
}
//在静态链表L中的链表中第i个元素之间插入新的数据元素e

Status ListInsert(StaticLinkList L, int i, ElemType e)
{
    
    
	int j, k, l;

	k = MAX_SIZE - 1;//存放数组(包含空表的)最后一个元素
	if (i<1 || i>ListLength(L) + 1)
	{
    
    
		return ERROR;
	}

	j = Malloc_SLL(L);//j就是空闲表的第一个下标

	if (j)
	{
    
    
		L(j).data = e;
		for (l = 1; l < i - 1; l++)
		{
    
    
			k = L[k].cur;//让k指向下一游标,直到
				//插入元素的前一个元素位置(k就是下标,会保存在上一元素的游标)
		}
		L[j].cur = L[k].cur;//原来是下标为k的元素连接下一
							//个元素的;现在变为插入元素指向
							//下一元素
		L[k].cur = j;//下标为k的元素连接下一元素为下标j,也就是指向了
					 //插入元素

		return OK;
	}
	return ERROR;
}

50、静态链表的删除操作怎么样?
答:基本思路和插入类似,修改修改游标数值以改变链接的方式。
ex:

//删除链表L中的第i个元素
Status ListDelete(StaticLinkList L, int i)
{
    
    
	int j, k;

	if (i<1 || i>ListLength(L))
	{
    
    
		return ERROR;
	}

	k = MAXSIZE - 1;

	for (j = 1; j <= i - 1; j++)
	{
    
    
		k = L[k].cur;//目的是找到需要删除的元素的前一元素的下标k
	}

	j = L[k].cur;//找到删除的元素的下标j
				 
	L[k].cur = L[j].cur;//将删除元素的游标赋值给其前一元素的游标

	Free_SLL(L, j);

	return OK;
}
//将下标为k的空间结点回收到备用链表
void Free_SLL(StaticLinkList space, int k)
{
    
    
	space[k].cur = space[0].cur;//将新增空点链接原有的空表处
	space[0].cur = k;//空表类似头位置指向刚插入的空点处
}

20201111
51、静态链表有啥优缺点?
答:
1)优点:
在插入和删除操作时,只需要修改游标,不需要移动元素,从而改进在顺序存储结构中插入和删除需要移动大量元素的缺点。
2)缺点:
–没有解决连续存储分配(数组)带来的表长难以确定的问题
–失去了顺序存储结构中随机存取的特性

总的来说,静态链表其实是为了给没有指针的编程语言设计的一种简单实现单链表功能的方法。静态链表的思考方式非常巧妙,应该理解其思想。

52、快慢指针是啥?
答:设计两个指针来遍历一个表,然后两个指针单次移动步数不一样,所满足一些功能需要。
如:快速查找一个未知长度链表的中间元素

Status GetMidNode(LinkList L, ElemType* e)
{
    
    
	LinkList search, mid;
	mid = search = L;

	while (search->next != NULL)
	{
    
    
		//search移动的速度是mid的2倍
		if (search->next->next != NULL)
		{
    
    
			search = search->next->next;//每次移动两次
			mid = mid->next;//每次移动一次
		}
		else
		{
    
    
			search = search->next;
		}
		*e = mid->data;//找到链表的中间位置并返回其数值
		return OK;
	}
}

53、单链表怎么变到循环链表?
答:
将单链表中的最后结点的指针由空指针改为指向头结点,就可以使得单链表连成一个环,这种头尾相连的单链表,简称循环链表。

54、循环链表的初始化、插入结点、删除结点代码如何实现?
答:

//初始化循环链表
//链表存储结构定义
typedef struct CLinkList
{
    
    
	int data;
	struct CLinkList* next;
}node;

void ds_init(node** pNode)
{
    
    
	int item;
	node* temp;
	node* target;

	printf("输入结点的值,输入0完成初始化\n");

	while (1)
	{
    
    
		scanf("%d", &item);
		fflush(stdin);//清除缓冲区

		if (item == 0)
			return;//输入0,表示退出循环

		if ((*pNode) == NULL)//判断是否为空链表
		{
    
    
			//循环链表中只有一个结点
			*pNode = (node*)malloc(sizeof(struct CLinkList));
			if (!(*pNode))
				exit(0);//如果新结点生成,则退出循环
			(*pNode)->data = item;
			(*pNode)->next = *pNode;
		}
		else
		{
    
    
			//找到next指向第一个结点的结点
			for (target = (*pNode); target->next != (*pNode); target = target->next)
				;

			//生成一个新结点
			temp = (node*)malloc(sizeof(struct CLinkList));

			if (!temp)
				exit(0);//如果新结点生成失败,则退出循环

			temp->data = item;//保存输入值
			temp->next = *pNode;//头插法插入链表中
			target->next = temp;//target移动到链表最前方
		}
	}
}

//插入结点
void ds_insert(node** pNode, int i)
{
    
    
	node* temp;
	node* target;
	node* p;
	int item;
	int j = 1;

	printf("输入要插入结点的值:");
	scanf("%d", &item);

	if (i == 1)
	{
    
    
		//新插入的结点作为第一个结点
		temp = (node*)malloc(sizeof(struct CLinkList));

		if (!temp)
			exit(0);

		temp->data = item;

		//寻找最后一个结点
		for (target = (*pNode); target->next != (*pNode); target = target->next)
			;
		temp->next = (*pNode);//原头结点链接到新结点后面
		target->next = temp;//最后结点的指针域指向新结点
		*pNode = temp;//移动头指针到新头结点上
	}
	else
	{
    
    
		target = *pNode;//指向链表第一个结点

		for (; j < (i - 1); ++j)
		{
    
    
			target = target->next;
		}//找到插入位置的前一个结点,并target指针指向它

		temp = (node*)malloc(sizeof(struct CLinkList));

		if (!temp)
			exit(0);//新结点生成失败,则退出

		//插入操作
		temp->data = item;//保存插入值
		p = target->next;//保存老结点位置
		target->next = temp;//插入
		temp->next = p;//链接老结点位置
	}
}

//删除结点
void ds_delet(node** pNode, int i)
{
    
    
	node* target;
	node* temp;
	int j = 1;

	if (i == 1)
	{
    
    
		//删除的是第一个结点
		//找到最后一个结点
		for (target = *pNode; target->next != *pNode; target = target->next)
			;
		
		temp = *pNode;//temp指向链表头结点
		*pNode = (*pNode)->next;//表头指针后移一个位置,也就是指向了第二结点
		target->next = *pNode;//表尾指针指向链表的第二位置,也就是新链表的
							  //表头,此时完成删除了第一个结点
		free(temp);//释放删除结点的内存
	}
	else
	{
    
    
		target = *pNode;//指向表头

		for (; j < i - 1; ++j)
		{
    
    
			target = target->next;
		}//找到要删除的结点的前一个结点位置

		temp = target->next;//记录所需要删除结点位置
		target->next = temp->next;//把要删除结点的前结点链接到要删除的结点的后结点
								  //,从而完成删除操作
		free(temp);//释放删除结点
	}
}

//返回节点所在位置
int ds_search(node* pNode, int elem)
{
    
    
	node* target;
	int i = 1;

	for (target == pNode; target->data != elem && target->next != pNode; ++i)
	{
    
    
		target = target->next;
	}

	if (target->next == pNode)//表中不存在该元素
		return 0;
	else
		return i;//找到,则返回其在链表中位置
}

56、约瑟夫问题是啥?怎么程序实现?
答:
1)约瑟夫问题:41个人中,39人约定围一圈,数到3,那个人就必须自杀;约瑟夫和朋友不想死,但是不敢违抗。所以,约瑟夫把自己和朋友分别安排在第16个位置和31个位置,从而躲避了死亡。

2)代码实现:

//n个人围圈报数,报m出列,最后剩下的是几号?
#include<stdio.h>
#include<stdlib.h>

typedef struct node
{
    
    
	int data;
	struct node* next;
}node;

node* create(int n)//创建链表过程
{
    
    
	node* p = NULL, * head;
	head = (node*)malloc(sizeof(node));
	p = head;//p指向当前结点的指针,这里head是头结点
	node* s;
	int i = 1;

	if (0 != n)//条件等同于   n!=0 
	{
    
    
		while (i <= n)
		{
    
    
			s = (node*)malloc(sizeof(node));
			s->data = i++;//为循环链表初始化,第一个结点为1,第二个结点为2
			p->next = s;//后接新插入的结点
			p = s;//指向最后结点位置
		}
		s->next = head->next;//将最后一个结点连接到第一个结点
	}
	free(head);//释放头结点,注意这里的头结点下一个才是第一个结点

	return s->next;//返回头结点,由于head头结点去掉了,
				   //所以此时的第一个结点(s->next)就是头结点
}

int main()
{
    
    
	int n = 41;//一共41个人
	int m = 3;//数到3的循环
	int i;
	node* p = create(n);
	node* temp;

	while (p != p->next)//表示还没有到最后一个人都要循环
	{
    
    //举例子,a1,a2,a3,a4。。。。,一开始p指向a1
		for (i = 1; i < m - 1; i++)
		{
    
    
			p = p->next;//p指向了a2
		}//这个循环就执行一次,指向下一个结点

		printf("%d->", p->next->data);//打印输出值a3(输出目标值,就是谁give over了)
		temp = p->next;//保存a3的地址(保存出局者)
		p->next = temp->next;//将a2连接到a4(删除出局者结点,形成新循环链表)

		free(temp);//释放a3(释放出局者的内存空间)

		p = p->next;//p指向了a4,开始第二轮循环(指向下一次循环的开始的结点)
	}
	printf("%d\n", p->data);//这里表示链表中最后一个结点输出
							//(因为前面都输出了,只剩一个结点)

	return 0;
}

57、循环链表有啥特点?
答:
无须增加存储量,仅对连接方式(指针指向)稍作改变,即可使得表处理更加灵活方便。

58、单链表的有环定义?
答:单链表的尾结点指向了链表的某个结点。(这里个人理解,有环单链表不包含循环链表类型)。

59、单链表有环的判断方法有哪些?
答:
1)比较步数法;
2)快慢指针法。

//比较步数法
int HasLoop1(LinkList L)
{
    
    
	LinkList cur1 = L;//定义结点cur1
	int pos1 = 0;//cur1的步数

	while (cur1)
	{
    
                             //cur1结点存在
		LinkList cur2 = L;    //定义结点cur2
		int pos2 = 0;         //cur2的步数

		while (cur2)    //cur2结点不为空
		{
    
    
			if (cur2 == cur1) //当cur1与cur2到达相同结点时
			{
    
    
				if (pos1 == pos2)//走过的步数一样
					break;       //说明没有环
				else {
    
    
					printf("环的位置在第%d个节点处。\n\n", pos2);
					return 1;
				}
			}
			cur2 = cur2->next;//如果没有发现环,继续下一个结点
			pos2++;   //cur2步数自增
		}
		cur1 = cur1->next;//cur1继续向后一个结点
		pos1++;//cur1步数自增
	}
	return 0;
}

//快慢指针
int HasLoop2(LinkList L)
{
    
    
	int step1 = 1;
	int step2 = 2;
	LinkList p = L;
	LinkList q = L;

	while (p != NULL && q != NULL && q->next != NULL)
	{
    
    
		p = p->next;//p每次走一步
		if (q->next != NULL)
			q = q->next->next;//q每次走两步

		printf("p:%d,q:%d\n", p->data, q->data);

		if (p == q)//如果有环,那么跑得快的指针,总会在某一个结点处和跑得慢的指针相遇
			return 1;//返回值1,表示存在有环,(return本身具有终止函数的执行功能)
	}
	return 0;
}

60、魔术师发牌问题是啥?
答:
感性描述:魔术师将一副牌中的13张黑牌,预先将她们顺序叠好,牌面朝下。魔术师将最上面的牌数1,翻过来,刚好是黑桃A,将黑桃A放到桌面上;第二次,从1数到2,正好是黑桃2,当然,前面的第一个牌是放在最下面了,依次类推,把13张牌准确翻出。
衍生一个问题,牌的开始顺序到底是怎么安排的。

//魔术师发牌问题
#include<stdio.h>
#include<stdlib.h>

#define CarNumber 13

typedef struct node {
    
    
	int data;
	struct node* next;
}sqlist,*linklist;

linklist CreateLinkList()
{
    
    
	linklist head = NULL;
	linklist s, r;
	int i;

	r = head;

	for (i = 1; i <= CarNumber; i++)
	{
    
    
		s = (linklist)malloc(sizeof(sqlist));
		s->data = 0;//初始化链表中每个数据都为0

		if (head == NULL)
			head = s;
		else
			r->next = s;

		r = s;
	}
	r->next = head;
	return head;
}

//发牌顺序计算
void Magician(linklist head)
{
    
    
	linklist p;
	int j;
	int Countnumber = 2;//第二次数数,是从2开始数的

	p = head;
	p->data = 1;//第一张牌放1

	while (1)
	{
    
    
		for (j = 0; j < Countnumber; j++)
		{
    
    
			p = p->next;
			if (p->data != 0)//该位置有牌的话,则下一位置
			{
    
    
				j--;//这里减一次,保证for循环准确进行
					//因为,这里如何有了数字,那么就跳过不算这一格了,继续下一个计算
			}
		}

		if (p->data == 0)
		{
    
    
			p->data = Countnumber;//如果这里还没有牌,那么把该牌放到这里面来
			Countnumber++;//下一张牌准备

			if (Countnumber == 14)//如果超出13张牌,那么发牌结束
				break;
		}
	}
}

//销毁工作
void DestoryList(linklist* list) {
    
    
	linklist ptr = *list;
	linklist buff[CarNumber];
	int i = 0;

	while (i < CarNumber)
	{
    
    
		buff[i++] = ptr;
		ptr = ptr->next;
	}

	for (i = 0; i < CarNumber; ++i)
		free(buff[i]);

	*list = 0;
}//良好编程习惯之一,手动销毁垃圾内存

int main()
{
    
    
	linklist p;
	int i;

	p = CreateLinkList();
	Magician(p);

	printf("按如下顺序排列:\n");
	for (i = 0; i < CarNumber; i++)
	{
    
    
		printf("黑桃%d", p->data);
		p = p->next;
	}
	DestoryList(&p);

	return 0;
}

#################################
不积硅步,无以至千里~
上面是个人学习过程中的笔记与理解,欢迎各位大佬批评指正。

猜你喜欢

转载自blog.csdn.net/qq_45701501/article/details/109374112
今日推荐