数据结构每日一算法题

数据结构算法题

❀本篇是关于计算机考研大师兄每日算法学习笔记


  1. 在带头结点的单链表L中,删除所有值为x的结点,并释放其空间,假设值为x的结点不唯一,试编写算法以实现上述操作。

【算法思想】:
删除值为x的结点,首先需要找到其前驱结点。
通常需要用到3个指针

pre用于指向前驱
p用于遍历整条链表
q指向当前找到的值为x的结点

在这里插入图片描述

void Del_x(LinkList &L,Elemtype x){
	//指针初始化
	Lnode *p=L->next;
	Lnode *pre=L;
	Lnode *q;


	while(p!=NULL){
		if(p->data==x){
			q=p;
			p=p->next;
			pre->next=p;//删除掉q结点后,防止断链
			free(q);
		}
		else{   //即当前p所指的结点的值不为x,则两个结点的值都需要往后移动一位
			pre=p;
			p=p->next;		
		}
	}
}


  1. 对长度为n的顺序表L,编写一个时间复杂度为O(n)、空间复杂度为O(1)的算法,该算法删除线性表中所有值为x的数据元素。

注意:
时间复杂度为O(n)即 只能扫描1次顺序表;
空间复杂度为O(1)即 只能申请常数个辅助空间。

基础分析:

  • 找到值为x的元素;
  • 删除(后面元素前移)

①【算法思想】:
遍历1次顺序表,将其中 x 的个数 记录为k,并将不为x的元素向前移动k个单位

void Del_x(sqList &L,ElemType x){
	int k=0;
	int i=0;
	
	for(i=0;i<L.length;i++){	
		if(L.data[i]==x){
			k++;
		}
		else{							//即不为x的元素
			L.data[i-k]=L.data[i];		//将不为x的元素向前移动k个单位
		}
	}
	L.length=L.length-k;
}

②【算法思想】:
遍历顺序表时,保留不是x的值。(即k记录的是保留的个数)

void Del_x(sqList &L,ElemType x){
	int k=0;
	int i=0;
	
	for(i=0;i<L.length;i++){	
		if(L.data[i]!=x){				//即不为x的元素,应该保留
			L.data[k]=L.data[i];
			k++
		}
	L.length=k;
}

  1. 将两个有序顺序表合并为一个新的有序顺序表,并由函数返回结果顺序表。

【算法思想】:

  • A、B中 逐一元素比较,将较小元素存入C中。
    当A、B其中有一个读完,则把另一个剩余部分依次放入C中。
bool Merge(sqList A,sqList B,sqList C){
	//判断C的长度是否符合
	if(A.length+B.length>C.MaxSize)
		return false;
	int i=0,j=0,k=0;

	while(i<A.length && j<B.length){
		if(A.data[i]<B.data[j]){
			C.data[k]=A.data[i];
			i++;
			k++;
		}
		else{
			C.data[k]=B.data[i];
			j++;
			k++;
		}
	}
	//若A中还有剩余
	while(i<A.length)
		C.data[k++]=A.data[i++];
		
	//若B中还有剩余
	while(i<B.length)
		C.data[k++]=B.data[j++];	

	C.length=k;
	return true;
}

  1. 设计一个高效算法,将顺序表L的所有元素逆置,要求算法的空间复杂度为O(1)

注意:空间复杂度O(1)即辅助空间顶多只能是常数个。

【方法】:
①常规方法
【算法思想】:
将前面一半的元素依次对应的和后面一半的元素交换。
在这里插入图片描述

void Reverse(sqList &L){
	Elemtype temp;  //用于交换的辅助变量
	
	for(int i=0;i<L.length/2;i++){
		temp=L.data[i];
		//L.length代表顺序表长度,等于n+1
		L.data[i]=L.data[L.length-i-1];
		L.data[L.length-i-1]=temp;
	}
}

②递归法
(好处:写出来的代码更简洁)
在这里插入图片描述

//导入数组A
void Reverse(int *A,int low,int high){
	if(low<high){
		swap(A[low],A[high]);
		Reverse(A,low+1,high-1);
	}	
}
void swap(int *x, int *y){
	int temp;
	temp = *x;
	*x = *y;
	*y = temp;
}

  1. 从顺序表中删除其值在给定值s与t之间(包含s和t,要求s<t)的所有元素,如果s或t不合理或顺序表为空,则显示出错信息并退出运行。

【算法思想】:
遍历顺序表,将s到t的之间值的个数 记为k,将不是s到t之间的数向前移动k个单位。

bool Del_st(sqList &L,ElemType s,ElemType t){
	int k=0;
	int i=0;
	
	if(L.length==0 || s>=t){
		return false;
	}
	
	for(i=0;i<L.length;i++){	
		if(L.data[i]>=s && L.data[i]<=t){
			k++;
		}
		else{							
			L.data[i-k]=L.data[i];		
		}
	}
	L.length=L.length-k;
	return true;
}
  1. 从有序顺序表中删除所有其值重复的元素,使表中所有元素的值均不同

在这里插入图片描述

【算法思想】:

  • 由于是有序的顺序表,因此重复的元素一定是相邻的
  • 设置两个指针,如下:
i所指的即需要保留的
j工作指针:用于遍历顺序表
  • 若i、j 所指元素值不相同,则代表该元素需要保留;若i、j相同,则代表该元素不保留

  • 第一个元素一定不重复,因此工作指针j 初始可以指向第二个元素。

bool Del_same(sqList &L){
	if(L.length==0)
		return false;

	//注意j是工作指针,用于遍历顺序表
	for(i=0,j=1;j<L.length;j++){	
		//判断需要保留的元素
		if(L.data[i]!= L.data[j]){
			//注意保留时,i指针应该向后移动一位
			L.data[++i]= L.data[j];
		}
	}

	//i即保留的元素(数组长度是0开始,到i)
	L.length=i+1;
	return true;
}
  1. 已知在一维数组A[m+n]中依次存放两个线性表(a1, a2, a3… am)和(b1, b2,b3… bn) 。试编写一个函数,将数组中两个顺序表的位置互换,即将(b1, b2,b3…bn)放在(a1, a2,a3… am)的前面。

【算法思想】:
使用3次逆置:
①(a1, a2, a3… am)逆置;
②(b1, b2,b3…bn)逆置;
③m+n整体逆置。

void Reverse(int A[]int from,int to){
	int i,temp;
	
	for(i=0;i<(to-from+1)/2;i++){
		temp=A[from+i];
		A[from+i]=A[to-i];
		A[to-i]=temp;
	}	
}


void exchange(int A[]int m,int n){
	Reverse(A,0,m-1);
	Reverse(A,m,m+n-1);
	Reverse(A,0,m+n-1);
}

设将n(n> 1)个整数存放到一维数组R中。设计一个在时间和空间两方面都尽可能高效的算法。将R中保存的序列循环左移p(0<p<n)个位置,即将R中的数据由(X0,X1,…,Xn-1)变换为(Xp,Xp+1,…,Xn-1,X0,X1,…,Xp-1)要求:
1)给出算法的基本设计思想。
2)根据设计思想,采用C或C++或Java语言描述算法,关键之处给出注释。
3)说明你所设计算法的时间复杂度和空间复杂度。

在这里插入图片描述
在这里插入图片描述

【算法思想】:
同第7题

void Reverse(int R[]int from,int to){
	int i,temp;
	
	for(i=0;i<(to-from+1)/2;i++){
		temp=R[from+i];
		R[from+i]=R[to-i];
		R[to-i]=temp;
	}	
}


void exchange(int R[]int p,int n){
	Reverse(R,0,p-1);
	Reverse(R,p,n-1);
	Reverse(R,0,n-1);
}

时间复杂度O(n),空间复杂度O(1)。

试编写带头节点的的单链表L中删除一个最小值节点的高效算法(假设最小值
节点是唯一的)

注:单链表算法时一定要注意【是否带有头节点】

【分析】:
①找:找最小值

利用两个指针

p遍历链表
q标记最小值结点

②删

其前驱指向其后继;free§。

因此需要设置前驱指针

ppre指向p的前驱指针
qpre指向q的前驱指针

【算法思想】:

  • 需要用到4个指针(见上述表格);
  • 遍历 过程中,若p.data<q.data,则将p、ppre分别赋值给q、qpre;
  • 遍历 完毕后,q指向最小值结点,qpre指向最小值结点的前驱。
LinkList Del_min(LinkList &L){
	Lnode *ppre=L;
	Lnode *p=ppre->next;
	Lnode *qpre=ppre;
	Lnode *q=p;

	//找
	while(p){
		if(p->data<q.data){
			q=p;
			qpre=ppre;
		}
		else{
			ppre=p;
			p=p->next;
		}	
	}

	//删
	qpre->next=q->next;
	free(q);
}
  1. 请使用头插法建立单链表。

【分析】:
特点——输入的数据次序 与 生产链表的结点数值次序 相反。

在这里插入图片描述

LinkList List_HeadInsert(LinkList &L){
	Lnode *s;
	int x;
	//创建头结点,动态申请空间
	LinkList L=(LinkList)malloc(sizeof(Lnode));
	L->next=NULL;
	scanf("%d",&x);
	
	while(x!=-1){
		s=(Lnode *)malloc(sizeof(Lnode));
		s->data=x;
		
		s->next=L->next;  		//将新结点插入表中-1
		L->next=s;				//将新结点插入表中-2

		scanf("%d",&x);			//继续输入新元素(直到输入-1结束)
	}
	return L;
}

使用尾插法建立单链表。

【分析】:
特点——输入的数据次序 与 生产链表的结点数值次序 相同。
在这里插入图片描述

LinkList List_TailInsert(LinkList &L){
	Lnode *s;
	int x;
	//创建头结点,动态申请空间
	LinkList L=(LinkList)malloc(sizeof(Lnode));
	*r=L;
	scanf("%d",&x);
	
	while(x!=-1){
		s=(Lnode *)malloc(sizeof(Lnode));
		s->data=x;
		
		r->next=s;  		
		r=s;				//r始终指向表尾

		scanf("%d",&x);			//继续输入新元素(直到输入-1结束)
	}
	r->next=NULL;				//表尾置空
	return L;
}

试编写算法将带头节点的单链表就地逆置,所谓的“就地”是指辅助空间的复杂
度为O (1)。

【分析】:
看到逆置,优先考虑到头插法;

方法一:头插法

需要两个指针:

r标记剩下链表的第一个节点(防止断链)
p
LinkList Reverse_LinkList(LinkList &L){
	Lnode *p=L->next;
	Lnode *r;
	L->next=NULL;
	
	while(p){
		r=p->next;			//	保留后继,防止断链
		p->next=L->next;
		
		L->next=p;
		p=r;
		
	}
}

方法二:调整指针

需要三个指针:

pre标志p的前驱
p
r标记p的后继

在这里插入图片描述
循环终止条件:p没有后继(即r为空)。

LinkList Reverse_LinkList(LinkList &L){
	Lnode *pre=L->next;
	Lnode *p=pre->next;
	Lnode *r=p->next;
	
	while(r!=NULL){
		
		//遍历链表,并将指针逆置
		p->next=pre;
		pre=p;
		p=r;
		r=r->next;		
	}
		L->next=p;
		return L;
}

设在一个带表头结点的单链表中所有元素的结点的数据值无序,
试编写一个函数,删除表中所有介于给定的两个值(作为函数的参数给出)之间的元素的元素(若存在)。

【算法思想】:
遍历链表,删除在最小值和最大值之间的元素。

void Del_LinkList(LinkList &L,int min,int max){
	Lnode *pre=L;
	Lnode *p=L->next;

	while(p!=NULL){
		if(p->data>min && p->data<max){
			pre->next=p->next;
			free(p);
			p=pre->next;
		}
		else{
			pre=p;
			p=p->next;
		}
	}		
}

给定两个单链表,编写算法找出两个单链表的公共结点。

【分析】:

(则两个单链表应该是Y型)
在这里插入图片描述
❗注意:两个单链表未说明为等长,因此应该思考其不等长(结果才具有普遍性)

①遍历法->暴力法

【算法思想】:

pLa的指针
qLb的指针
  • 保持其中一个指针不动(该处假定p不动),另一个指针遍历(q遍历);则比较两个指针(即p=q); 若不等于则q向后移动一位;若q遍历完整个数组,还未相等,则p向后移动一位。
  • 则需 两次循环。
Fun_Search(LinkList &La,LinkList &Lb){
	LNode *p=La->next;
	LNode *q=Lb->next;

	while(p!=NULL){
		while(q!=NULL){
			if(p==q)
				return p;		//找到公共结点,并返回
			q=q->next;
		}
		p=p->next;
		q=Lb->next;
	}
}

时间复杂度:O(La * Lb)

②“双指针问题”

【算法思想】:

将问题转换成 La、Lb如何同时到达表尾,再进行p==q的比较
在这里插入图片描述
在这里插入图片描述

LinkList Search_Common(LinkList &La,LinkList &Lb){
	LNode *p;
	LNode *q;
	int LenA=length(La);		
	int LenB=length(Lb);		

	if((LenA-LenB)>0){
		k=LenA-LenB;		//求出两个链表只差
		p=La->next;
		q=Lb->next;
	}else(){
		k=LenB-LenA;
		p=Lb->next;
		q=La->next;
	}

	//始终让较长的移动k位
	while(k--){
		p=p->next;			//让p先向后面移动k位,此时p、q剩余的表长相同
	}
	while(p!=NULL){
		if(p==q){
			return p;		//找到公共结点,并返回
		}else{				//若没找到公共结点,则同步向后移动(保证同时到达表尾)
			p=p->next;
			q=q->next;
		}
	}
	return NULL//没找到公共结点,返回空
}

时间复杂度:O(LenA + LenB)

将一个带头结点的单链表A分解为两个带头结点的单链表A和B,使得A表中含
有原表中序号为奇数的元素,而B表中含有原表中序号为偶数的元素,且保持
其相对顺序不变。

【分析】:
尾插法
在这里插入图片描述
【算法思想】:

  • 不断使用尾插法,依次生成链表A、B。注意A链表首先置空
  • 定义i,通过i%2==0判断奇偶性
LinkList Create(LinkList &A){
	int i=0;					//通过i判断奇偶性
	LinkList B=(LinkList)malloc(sizeof(Lnode));
	B->next=NULL;
	LNode *ra,rb=B;
	LNode *p=A->next;
	A->next=NULL;				//置A链表为空(这一步和上一步不可颠倒)

	while(p!=NULL){
		i++;
		if(i%2==0){
			rb->next=p;
			rb=p;				//若是偶数,插入B中
		}else{
			ra->next=p;
			ra=p;				//若是奇数,插入A中
		}
		p=p->next;
	}
	ra->next=NULL;
	rb->next=NULL;				//两链表末尾置空
	return B;					//A链表已有,因此只需要返回B链表
}

设C={a1, b1, a2, b2, …an, bn}为线性表,采用带头结点的hc单链表存放,设计一个就地算法,将其拆分为两个线性表,使得A={a1, a2, … an},B= {bn,… b2, b1}。

法①

【分析】:
在这里插入图片描述
❗注意:头插放断链,尾插留尾针

【算法思想】:
遍历C链表,将第一个结点尾插在A中,第二个结点头插在B中…依次如此,直到将C拆分为A、B。

LinkList Create(LinkList &hc){
	LNode *ra=hc;				//尾插法的尾指针
	LNode *p=hc->next;			//遍历指针
	LinkList A=(LinkList)malloc(sizeof(Lnode));
	LinkList B=(LinkList)malloc(sizeof(Lnode));
	A->next=NULL;				//第一个结点初始化,置空
	B->next=NULL;
	
	A->next=NULL;				
	
	//交替尾插、头插、尾插、头插...
	while(p!=NULL){				//遍历整个C链表
		ra->next=p;				//A-尾插
		ra=p;	
		p=p->next;	
		if(p!=NULL){
			r=p->next;			//头插防断链
			p->next=B->next;	//B-头插	
			B->next=p;
			p=r;
		}
	return A;
	return B;					
}

法②
注:也可以使用15题的方法,定义i,通过i%2==0判断奇偶性;若奇数则尾插至A,若偶数则尾插至B。

在一个递增有序的线性表中,有数值相同的元素存在。若存储方式为单链表,设计算法去掉数值相同的元素,使表中不再有重复的元素,例如(7, 10, 10,21,30, 42, 42,42, 51, 70)将变成(7,10, 21, 30, 42, 51, 70)。

【分析】:
由于递增有序,则值域相同元素一定是相邻的。
“双指针问题”

法①多设置前驱指针pre

【算法思想】:
设置两个指针pre、p;
若两指针的值相同,则删除p指针。

void Del_Commondata(LinkList &L){
	LNode *pre=L->next;				//由于是删除相同元素,因此从第二个节点判断即可
	LNode *p=pre->next;				//p是遍历指针

	while(p!=NULL){
		if(pre->data==p->data){		//找到重复节点
			pre->next=p->next;
			free(p);				//释放值相同的节点
			p=pre->next;
		}else{						//不相同则两指针同时向后移
			pre=p;
			p=p->next;
		}
	}
}

法②多设置后继指针q

void Del_Commondata(LinkList &L){
	LNode *p=L->next;				
	LNode *q;				

	while(p->next!=NULL){
		q=p->next;
		if(p->data==q->data){		
			p->next=q->next;
			free(q);				
		}else{						
			p=p->next;
		}
	}
}

假设有两个按元素值递增次序排列的线性表,均以单链表的形式存储。请编写算法将这两个单链表归并为一个按元素值递减次序排列的单链表,并要求利用原本两个单链表的结点存放归并后的单链表。

【算法思想】:

  • A、B设置指针p、q,依次比较p->data与p->data,将较小的节点头插进合并链表中;
  • 直至遍历到其中一条为空,然后将剩下的链表依次头插即可。
LinkList merge(LinkList &A,LinkList &B){
	LNode *p=A->next;
	LNode *q=B->next;
	A->next=NULL;
	LNode *r;

	while(p!=NULL&&q!=NULL){
		if(p->data<q->data){
			r=p->next;
			p->next=A->next;
			A->next=p;
			p=r;
		}else{
			r=q->next;
			q->next=A->next;
			A->next=q;
			q=r;
		}
		while(q!=NULL){						//若B链表有剩余
			r=q->next;
			q->next=A->next;
			A->next=q;
			q=r;
		}
		while(p!=NULL){						//若A链表有剩余
			r=p->next;
			p->next=A->next;
			A->next=p;
			p=r;
		}
	}
}

设A和B是两个单链表(带头结点),其中元素递增有序。设计一个算法从A和B中的公共元素产生单链表C,要求不破坏A、B的结点。

【分析】:
在这里插入图片描述
【算法思想】:

  • 依次比较A、B元素,将元素值较小的指针往后移;
  • 若相等即为公共元素,则创建一个新结点等于两节点的值,使用尾插法插入到新链表C中;并将AB两指针同时向后移动1位;
  • 若其中一个有剩余,则无需处理。
LinkList Common(LinkList A,LinkList B){				//要求不破坏AB链表,因此可以不加取地址符
	LNode *p=A->next;
	LNode *q=B->next;
	LinkList C=(LinkList)malloc(sizeof(LNode));
	r=C;
	
	while(p!==NULL&&q!=NULL){
		if(p->data<q->data)
			p=p->next;
		else if(p->data>q->data)
			q=q->next;
		else{										//即找到了公共元素
			S=(LNode *)malloc(sizeof(LNode));		//生成一个新结点
			S->data=p->data;						//将公共值赋值给新结点
			r->next=s;								//尾插法
			r=s;
			p=p->next;								//两个元素均向后移动1位
			q=q->next;			
		}
	}
	r->next=NULL;									//C链表置空
}

※已知两个链表A和B分别表示两个集合,其元素递增有序排列。编制函数,求A和B的交集,并存放于A链表中。

【分析】:
在这里插入图片描述
【算法思想】:
(不符合要求的应删掉->边比较边释放空间)

  • 依次扫描A、B节点,比较扫描节点data域的值,将较小的指针向后移动,并释放空间;
  • 若两者相等,尾插到A链表,直至遍历到表尾;
  • 若A中有剩余,则逐个释放剩余元素(因为只要求保留公共元素即可,其他节点释放空间)。
LinkList Common_A(LinkList &A,LinkList &B){				
	LNode *p=A->next;
	LNode *q=B->next;
	LNode *r=A;										//尾插留尾指针
	A->next=NULL;
	LNode *u;
	
	while(p!==NULL&&q!=NULL){
		if(p->data<q->data)							//谁小释放谁
			u=p;			
			p=p->next;
			free(u);
		else if(p->data>q->data)
			u=q;
			q=q->next;
			free(u);
		else{										//即找到了公共元素
			r->next=p;
			r=p;									//保留p的同时,应该考虑释放q
			p=p->next;
			u=q;									//释放q
			q=q->next;
			free(u);
		}
	}
	while(p!=NULL){									//若A中有剩余,则将剩下全部释放
		u=p;
		p=p->next;
		free(u);	
	}
	while(q!=NULL){									//此处是讨论B中剩余,最好写上
		u=q;									//释放q
		q=q->next;
		free(u);
	}
	r->next=NULL;									//C链表置空
	free(B);
	return A;
}

两个整数序列A= a1,a2,a3… .am和B=b1, b2, b3… bn已经存入两个单链表中,设计一个算法,判断序列B是否是序列A的连续子序列。

①暴力法->枚举

【算法思想】:

  • 依次遍历A、B,比较p->data与q->data。
  • 若相等,p、q一起向后移;若不相等,则A从上次开始的比较节点的后继节点开始,而B从头节点的后继开始。
  • 直到B链表到末尾,表示匹配成功;若A链表到末尾,但B链表未到末尾,则匹配失败。
bool Compare_lianxu(LinkList A,LinkList B){
	LNode *p=A->next;
	LNode *q=B->next;
	LNode *pre=p;					//pre指针记录的是p节点的位置

	while(p!=NULL && q!=NULL){
		if(p->data != q->data){
			pre=pre->next;			//A从上次节点的后继节点 再次开始新一轮比较
			p=pre;
			q=B->next;
		}else{
			p=p->next;
			q=q->next;
		}
	}

	if(q=NULL)
		return true;
	else
		return false;
}

设计一个算法用于判断带头结点的循环双链表是否对称。

【分析】:
在这里插入图片描述
在这里插入图片描述

【算法思想】:

  • 让p从左往右扫描,让q从右往左扫描;直到p、q指向同一节点(偶数)或相邻(奇数);(->此处是把头结点算进)。则其是对称的返回1,否则返回0。
int Compare_Duichen(DLinkList L){
	DNode *p=L->next;
	DNode *q=L->prior;

	while(p!=q && q->next=p){			//也可以直接写为while(p!=q)
		if(p->data==q->data){
			p=p->next;
			q=q->prior;
		}else
			return 0;
	}
	return 1;
}

有两个循环单链表,链表头指针分别为h1和h2,编写一个函数将链表h2链接到链表h1之后,要求链接后的链表仍保持循环链表的形式。

【算法思想】:
分别为两个循环单链表 设置表尾指针p、q;

  • 找到h1的表尾p,h2的表尾q;
  • 将p指向h2,将q指向h1,重新组成一条循环单链表。
LinkList Add(LinkList &h1,LinkList &h2){
	LNode *p,*q;

	//寻找h1的表尾p
	p=h1;
	while(p->next!=h1){
		p=p->next;
	}
	//寻找h2的表尾q
	q=h2;
	while(q->next!=h2){
		q=q->next;
	}

	//修改p、q指针指向
	p->next=h2;
	q->next=h1;
}

设有一个带头节点的的循环单链表,其结点值均为正整数。设计一个算法,反复找出单链表中结点值最小的结点并输出,然后将该结点从中删除,直到单链表为空为止,再删除表头结点。

【算法思想】:
反复找出当前的最小值节点,并删掉,直到链表为空,释放头结点L。

❗注意是 反复 寻找。

void Del_All(LinkList &L){
	LNode *p,*pre,*minp,*minpre;			//先只将指针定义

	while(L->next!=L){						//保证“反复“查找最小值并删除
		p=L->next;					//注意每次开始循环一轮,都得进行指针的重置
		pre=L;
		minp=L->next;
		minpre=L;
		
		while(p!=L){							//一轮循环,删除一个最小值
			if(p->data<minp->data){
				minp=p;
				minpre=pre;
			}else{							
				pre=p;
				p=p->next;
			}
		}
		printf("%d",minp->data);
		minpre->next=minp->next;
		free(minp);
	}
}

设头指针为L的带有表头结点的非循环双向链表,其每个结点中除有pred(前驱指针)、data (数据)和next (后继指针)域外,还有一个访问频度域freq。
在链表被启用前,其值均初始化为零。每当在链表中进行一次Locate(L,x)运算时,令元素值为X的结点中freq域的值增加1,并使此链表中结点保持按访问频度非增(递减)的顺序排列,同时,最近访问的结点在频度相同的结点前面,以便使频繁访问的结点总是靠近表头。
试编写符合上述要求的Locate (L,x) 运算的算法,该运算为函数过程,返回找到结点的地址,类型为指针型。

【补充】:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

【算法思想】:

  • 找到含x的节点;
  • x节点的freq++;
  • 取出x;
  • 根据freq的大小,插入到第一个比之大的后面。(保证freq是递减的次序)
    在这里插入图片描述
LNode Locate(LinkList &L,ElemType x){
	LNode *p=L->next;
	LNode *q;

	while(p&&p->data!=x){			//在双循环链表中查找x
		p=p->next;					//p不为空或其值不为x则向后移
	}
	if(p==NULL{
		printf("不存在值为x的节点");
		exit(0);
	}else{							//即找到x。则开始后续操作(取下节点并插入)
		p->freq++;					//将其频率freq加1
		q=p->pred;					//从原节点的前驱节点开始
	}
	if(p->next!=NULL){				//判断p是否是最后一个节点
		p->next->pred=p->pred;		//取出p节点-2步
		p->pred->next=p->next;
	}else{
		p->pred->next=NULL;
	}

	//q往前找,找到第一个比之大的。然后插入
	while(q!=L && q->fred<=p->fred){			//查找p的插入位置
		q=q->fred;					//q往前找
	}
	p->next=q->next;			//插入q-4步
	q->next->pred=p;
	p->pred=q;
	q->next=p;
	return p;					//返回该节点
}

已知一个带有表头结点的单链表,结点的结构为
在这里插入图片描述

假设该链表只给出头指针List,在不改变链表的前提下,请设计一个尽可能高效的算法,
查找链表中的倒数第K个位置上的结点(k为正整数)。若查找成功,算法输出该结点data域的值,并返回1;否则,只返回0。要求:
1)描述算法的的基本设计思想。
2)描述算法的详细实现步骤。
3)根据设计思想和实现步骤,采用程序设计语言描述算法(使用C或者C++实现),关键之处给出简要注释。

❗注意:给了链域为link,则应该使用如p->next。

【分析】:
①找倒数第k个位置;

  • 法一:
    第一次遍历找到表的长度List.length;
    第二次遍历到List.length-k初。
  • 法二:
    申请一个数组;
    第一次遍历,将元素存入数组中;
    (在顺序表)中直接查找。O(1)
  • 法三:“间隔一定,同步后移”
    设置双指针;
    其中一个指针先移动k个位置;
    接着两个指针一起移动。

【算法思想】:
先将p指针移动到第k个位置;
然后p、q一起向后移动,直到p到达表尾,则q指向倒数第k个位置。

typedef struct LNode{
	ElemType data;
	struct LNode *Link;
}LNode;

int Search_k(LinkList List,int k){
	LNode *p=List->link;	p、q指向第一个节点
	LNode *q=List->link;
	int count=0;
	
	//p先移动,当移动到第k个位置时,q再开始移动(之后p、q一起移动)
	while(p!=NULL){
		if(count<k)
			count++;
		else
			q=q->link;
		p=p->link;
	}

	if(count<k)
		return 0;
	else{
		printf("%d",q->data);
		return 1;
	}	
}

[2012统考真题]假定采用带头结点的单链表保存两个单词有相同的后缀时,可享受相同的后缀存储空间,例如,“loading"being”的存 储映像如下图所示。.
在这里插入图片描述

设str1和str2分别指向两个单词的单链表的头结点,链表结点结构为[data][link],
请设计一个时间上尽可能高效的算法,找出由str1和str2所指向两个链表共同后缀的起始位置(如图中字符i所在结点的位置p)。要求:
1)给出算法的基本设计思想
2)根据设计思想,采用C或者C++或语言描述算法,关键之处给出注释。
3)说明你算法的时间复杂度。

【同第14题】:给定两个单链表,编写算法找出两个单链表的公共节点。

【分析】:
单链表的结构是X型还是Y型
在这里插入图片描述
【方法】:
法一:暴力法
双重循环:p移动一位,而q移动一轮地比较p、q。
法二:“间隔一定,同步后移”
(遍历一次)
将问题转换为如何同时到达表尾。

  • 等长时:p、q一起向后移动即可;
  • 不等长时:较长的先移动二者长度之差,然后再同时移动。

【算法思想】:

  • 求出str1和str2所指链表的长度m与n;
  • 将较长的移动k(k等于二者长度之差);
  • 反复将p、q指针向后移,直到p、q指向同一指针(p==q),即指向了同一后缀。
//求单链表的长度
int length(SNode *head){
	int len=0;
	LNode *p=head->next;
	while(p!=NULL){
		len++;
		p=p->next;
	}
	return len;
}

LinkList Search_Common(LinkList &str1,LinkList &str2){
	LNode *p;
	LNode *q;
	m=length(str1);
	n=length(str2);

	for(p=str1->next;m>n;m--)	//若m大,则p后移
		p=p->next;
	for(q=str2->next;m<n;n--)	//若n大,则q后移
		q=q->next;
	while(p!=NULL && p!=q){
		p=p->next;
		q=q->next;
	}
	if(p=NULL)
		return 0;
	else
		return p;
}

[2015统考真题]
用单链表保存m个整数,结点的结构为[data][link],且|data|≤n(n正整数)。
现要求设计一个时间复杂度尽可能高效的算法,对于链表中data的绝对值相等的结点,仅保留第一次出现的结点而删除其余绝对值相等的结点。
例如,若给定的单链表head如下:
在这里插入图片描述
要求:
1)给出算法的基本设计思想
2)使用C或者C++语言,给出单链表结点的数据类型定义。
3)根据设计思想,采用C或者C++语言描述算法,关键之处给出注释。
4)说明你所设计的算法的时间复杂度和空间复杂度。

【分析】:
①可能需要用空间换时间——开辟一个辅助数组。(重点是如何使用)

🚗处理方法:标记数组——将链表中的值转换为数组中的次序

  • 数组初始化:均为0;
  • 单链表中的data值,即存储为辅助数组的次序。如data值为21,则令C[21]=1。
  • 若后面再次获得data值为21时,扫描到数组C[21]=1,则直接删除。

②第一次的值保留,其余重复均删掉。(重点是取绝对值)

🚗处理方法:三目运算符
m=p->data>0?p->data : -p->data
(这样能保证m一定是一个正数)

【算法思想】:“以空间换时间”

  • 申请一个大小为n+1个辅助空间(因为|data|≤n),且初始化为0;
  • 第一次出现,保留(数组中的标记由0改为1);否则删掉节点。
typedef struct LNode{
	int data;
	struct LNode *Link;
}LNode;

void Del_Common(LinkList &head,int n){
	LNode *p=head->link;	//用于遍历
	LNode *pre=head;		//用于删除

	C=(int *)malloc(sizeof(int)*(n+1));		//申请辅助数组空间
	for(int i=0;i<n+1;i++)		//数组C初始化为0
		C.data[i]=0;

	while(p!=NULL){
		m=p->data>0?p->data:-p->data;
		if(C.data[m]==0){		//若标记为0,则为第一次出现,需保留
			C.data[m]=1;		//将标记改为1
			pre=p;				//在链表中保留该节点
			p=p->link;
		}else{					//重复,删除
			pre->link=p->link;
			free(p);
			p=pre->link;
		}
	}
	free(C);					//最后释放辅助数组
}

时间复杂度O(m)
空间复杂度O(n)

[2019统考真题]
设线性表L= (a1, a2,a3,…, an-2, an-1, an)采用带头结点的单链表保存,链表中的结点定义如下:

Typedef struct node{
	int data;
	struct node *next
} NODE;

请设计一个空间复杂度为O (1) 且时间上尽可能高效的算法,重新排列L中的各结点,得到线性表L1= (a1, an, a2, an-1,a3,an-2, … )。
要求:
1)给出算法的基本设计思想。
2)根据设计思想,采用C或C++语言描述算法,关键之处给出注释。
3)说明你设计的算法的时间复杂度。

【分析】:
在这里插入图片描述
由①到④到②最终实现③

【算法思想】:
在这里插入图片描述
①找单链表的中间节点;

  • ( 法一):
    第一次遍历找出length长度;第二次遍历找到length/2即中间节点。

  • (法二):
    “双指针法”——“让间隔保持倍数”——q指针向后移动1位而p指针向后移动2位,当p指针到达末尾时,则q指针指向中间节点。
    在这里插入图片描述

②将后半段链表逆置;(头插法)

在这里插入图片描述

③将后半段 插入到 前半段的合适位置。

  • (法一):
    两个半条的链表都 使用尾插法。
  • (法二):
    直接插入在要求的位置。

在这里插入图片描述

void Re_Link(LinkList &L){
	LNode *p,*q,*r,*s;
	p=L->next;
	q=L->next;

	//1.找中间节点
	while(q->next!=NULL){	
		p=p->next;				//p走1步
		q=q->next;
		if(q->next!=NULL)		//再进行一次判断,再使q走第2步
			q=q->next;
	}		

	//2.从q开始后半段均需逆置
	while(q!=NULL){
		r=q->next;				//头插防断链
		q->next=p->next;
		p->next=q;
		q=r;
	}

	//3.合并
	s=L->next;
	q=p->next;
	while(q!=NULL){
		r=q->next;				//防止断链
		q->next=s->next;		//这行和下行类似于头插法
		s->next=q;
		s=q->next;				//更改s的位置
		q=r						//防止断链
	}
}

在这里插入图片描述
在这里插入图片描述

  1. 已知一棵二叉树按顺序存储结构进行存储,设计一个算法,求编号分别为 i 和 i 的两个节点的最近公共祖先结点的值。

【考点知识】:
顺序表/链表----头结点计数0;
顺序二叉树----头结点计数1(为了维持逻辑关系)。

【分析】:
在这里插入图片描述

【算法思想】:
不断的将较大的取一半,比较i、j的大小,直到i=j。即找到了最近公共祖先节点。

ElemType Com_Ancestor(SqTree T,int i,int j) {
	if(T[i]!=NULL && T[j]!=NULL){
		while(i!=j){
			if(i>j)
				i=i/2;
			else
				j=j/2;
		}
		return [i];
	}	
}

二叉树的遍历算法:先序、中序、后序

【知识点】:
二叉树的链式存储结构
在这里插入图片描述

①先序

void Preorder(Bi TreeT){
	if(T!=NULL){
		visit(T);					//访问根结点
		Preorder(T->Lchild);		//访问左子树
		Preorder(T->Rchild);		//访问右子树
	}
}

②中序

void InOrder(Bi TreeT){
	if(T!=NULL){
		InOrder(T->Lchild);		//访问左子树
		visit(T);					//访问根结点
		InOrder(T->Rchild);		//访问右子树
	}
}

③后序

void PostOrder(Bi TreeT){
	if(T!=NULL){
		PostOrder(T->Lchild);		//访问左子树
		PostOrder(T->Rchild);		//访问右子树
		visit(T);					//访问根结点
	}
}

【总结】
①visit(T)的位置;
②递归

【时间复杂度和空间复杂度分析】:
在这里插入图片描述

中序递归算法转非递归算法。
在这里插入图片描述
【算法思想】:
使用栈!

  • 沿着根的左孩子,依次入栈,直到左孩子为空;
  • 接着将栈顶元素出栈,并访问;若右孩子为空,则继续执行;
  • 若右孩子不为空,则执行第一步
void InOrder(BiTree T){
	Initstack(s);
	BiTree p=T;

	while(p || IsEmpty(s)){
		if(p){					//若左孩子不为空,则一直向左走
			push(s,p);
			p=p->Lchild;
		}else{				
			pop(s,p);			//栈顶元素出栈并访问
			visit(p);
			p=p->Rchild;		//向右子树走
		}
	}
}

【记忆口诀】:
中序
在这里插入图片描述
先序:访问入栈向左走,出栈观察右子树
后序:入栈向左一直走,判定(右子树),出栈访问,

后序遍历的非递归算法。

【算法思想】:

  • 沿着根的左孩子,依次入栈,直到左孩子为空;
  • 读栈顶元素(判定),若其右孩子不空且未被访问,将右孩子执行第一步;
  • 若右子树为空或已被访问过,则栈顶元素出栈。
void PostOrder2(BiTree T){
	InitStack(s);
	BiTree p=T;
	BiTree r=NULL;
	
	while(p || IsEmpty(s)){
		if(p){
			push(s,p);
			p=p->Lchild;
		}else
			GetTop(s,p);
		if(p->Rchild&&p->Rchild!=r){		r标记最近访问的节点
			p=p->Rchild;
			push(s,p);
			p=p->Lchild;
		}else{	
			pop(s,p);				若右子树为空或被访问过则出栈
			r=p;
			p=NULL;
		}
	}
}

二叉树的层次遍历算法。

【算法思想】:
使用队列!

  • 将根结点入队,出队;
  • 访问出队节点,若它有左子树则将左子树入队,若它有右子树则将右子树入队…如此反复,直到队列为空。
void LevelOrder(BiTree T){
	InitQueue(Q);					//初始化队列
	BiTree p;
	EnQueue(Q,T);					//将根节点入队

	while(!IsEmpty(Q)){
		DeQueue(Q,p);
		visit(p);
		if(p->Lchild!=NULL)			//若有左子树,则将左子树入队
			EnQueue(Q,p->Lchild);
		if(p->Rchild!=NULL)			//若有右子树,则将右子树入队
			EnQueue(Q,p->Rchild);
	}
}

【主机口诀】:
在这里插入图片描述

试给出二叉树的自下到上、从右往左的层次遍历算法。

【算法思想】:“层次遍历的改造”

  • 利用原本的层次遍历算法,出队的同时将各节点入栈,再依次出栈。
void LevelOrder(BiTree bt){
	InitQueue(Q);					//初始化队列
	InitQueue(S);					//初始化栈
	BiTree p;

	if(bt!=NULL){
		EnQueue(Q,T);					//将根节点入队
	}
	while(!IsEmpty(Q)){
		DeQueue(Q,p);
		push(s,p);					//元素出队以后,入栈
		if(p->Lchild!=NULL)			//若有左子树,则将左子树入队
			EnQueue(Q,p->Lchild);
		if(p->Rchild!=NULL)			//若有右子树,则将右子树入队
			EnQueue(Q,p->Rchild);
	}
	while(!IsEmpty(S)){
		pop(s,p);					//将所有入栈的元素出栈
		visit(p->data);
	}
}

假设二叉树采用二叉链表存储结构,设计一个算法求二叉树的高度(递归和非递归)。

【分析】:




经验总结

【经验总结】
删除节点:得知道其前驱
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 1
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值