第二章 线性表

2-1

1、从顺序表中删除具有最小值的元素(假设唯一)并由函数返回被删元素值。空出位置由最后一个元素填补,若顺序表为空,显示出错信息并退出返回。--简单题

bool Delete_min_Value(SqList &L,Elem &e)//e用于保存被删除的值
{
  if(L.length==0) return false;
  int value=L.data[0];//假设第一个数为最小值
  int pos=0;//用pos记录最小值的位置
  for(int i=0;i<L.length;i++)
  {
     if(value>L.data[i]) 
      {  
        value=L.data[i];
        pos=i;
      }
  }
  e=value;
  L.data[pos]=L.data[L.length-1];
  L.length--;
  
  return true;
}

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

//解法一 此题我第一想法是再开一个顺序表 将原始表从0到L.length-1;新表从L.length-1到0 
//存储一遍 最后再复制回原表 以空间换时间

//王道解答:从中间断开  
void reverse(SqList &L)
{
	Elem temp;//用于交换的临时变量
	for(int i=0;i<L.length/2;i++)
	{
		temp=L.data[i];
		L.data[i]=L.data[L.length-i-1];
		L.data[L.length-i-1]=temp;
	 } 
  
} 

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

//此题我试着找等于x的个数 但边找却不能想到更新的方法(太菜了)
//王道解法:
void dele_1(SqList &L,Elem x)
{
	int k=0;//k代表x的个数
	for(int i=0;i<L.length;i++)
	{
		if(L.data[i]==x) k++;
		else L.data[i-k]=L.data[i];//这步非常巧妙 在扫描x的过程就更新了 
	 } 
	L.length-=k;//更新表长 
 } 
//反向思考 找不等于x的元素
void dele_2(SqList &L,Elem x)
{
	int k=0;//k代表不等于x的元素个数 
	for(int i=0;i<L.length;i++)
	{
		if(L.data[i]!=x)
		{
			L.data[k]=L.data[i];
			k++;//只有不等于x时 才更新k值 
		}
	}
	L.length=k;
 } 

4、从有序表中删除其值在给定值s与t之间(要求s<t)的所有元素,若s或t不合理或顺序表为空,则显示处错误信息并退出运行。

//受上一题启发 找不在该区间的元素
bool dele_s_t(SqList &L,Elem s,Elem t)
{
	if(s>t||L.length==0) return false;
	
	int k=0;//不在s-t之间的元素个数 
	for(int i=0;i<L.length;i++)
	{
		if(L.data[i]<s||L.data[i]>t)
		{
			L.data[k]=L.data[i];
			k++;
		}
	}
	L.length=k;//更新表长度 
	return true;
 } 


//解法二 
//由于是有序顺序表 可以先找到大于等于s的第一个数 再找到大于t的第一个数
//确定这两个数的位置 再移动元素即可
bool Dele_2(SqList &L,ElemType s,ElemType t)
{
	int i,j;//i代表大于等于s的第一个元素的位置 j代表大于t的第一个元素的位置
	
	//判断删除位置是否合理
	if(s>t||L.length==0) return false;//s>t 或顺序表为空时 程序终止
	
	//找大于等于s的第一个位置
	for(i=0;i<L.length&&L.data[i]<s;i++);//注意是'<' 
	
	//找大于t的第一个数
	for(j=i;j<L.length&&L.data[i]<=t;j++);//注意是'<=' 
	
	for(;j<L.length;i++;j++) L.data[i]=L.data[j];//前移 
	
	L.length=i;//因为出循环后i加1 正好代表顺序表长度
	
	return true; 
	
 } 

5、从顺序表中删除其值在给定值s与t之间(要求s<t)的所有元素,若s或t不合理或顺序表为空,则显示处错误信息并退出运行。

//思路:因为顺序表是无序的 找不在给定区间中的元素即可
//有两种写法 可以参考第四题中解法一的写法 下面给出另一种写法 
bool Dele_3(SqList &L,ElemType s,ElemType t)
{
	int k=0;//记录在所给区间中元素的个数
	
	if(s>t||L.length==0) return false;//s与t不合理 或顺序表为空
	
	for(int i=0;i<L.length;i++)
	{
		if(L.data[i]>=s&&L.data[i]<=t) k++;
		else
		{
			L.data[i-k]=L.data[i];//根据更新过程中k的个数 移动元素 
		}
	}
	L.length-=k;//修改顺序表长度
	
	return true; 
 } 

6、从有序表中删除所有其值重复的元素,使表中所有元素的值均不同

//删除顺序表中所有重复元素---双指针去重 
bool Dele_Same(SqList &L)
{
	int i,j;//双指针 
	
	if(L.length==0) return false;
	
	for(i=0,j=1;j<L.length;j++)
	{
		if(L.data[i]!=L.data[j])//若相等 则代表重复 让j++ 
		{
			L.data[++i]=L.data[j];//当前元素与下一个元素不相等 才移动元素 
		}
	}
	L.length=i+1;
	
	return true;
 }

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

//合并有序顺序表 --典型双指针应用
bool Merge_List(SqList L1,SqList L2,SqList &L)//L1、L2分别表示有序表1、2 L表示新顺序表  
{
	if(L1.length+L2.length>L.length) return false;//这一步判断感觉挺牵强的。。。 
	
	int i,j,k=0;//维护三个指针
	
	for(i=0,j=0;i<L1.length&&j<L2.length;i++,j++)
	{
		
		if(L1.data[i]<=L1.data[j])
		{
			L.data[k++]=L1.data[i];
		}else
		{
			L1.data[k++]=L2.data[j];
		} 
		
	} 
	
	//剩下一个表没处理完 直接将剩下的元素移入新表 
	while(i<L1.length) L.data[k++]=L1.data[i++];
	while(i<L2.length) L.data[k++]=L2.data[j++];
	
	L.length=k;
	
	return true;
	
 } 

8、已知一位数组A[M+N]中依次存放两个线性表(a1 a2 .. am)和(b1 b2..bn)。编写一个算法,将数组中两个顺序表的位置互换,即将(b1 b2..bn)放在(a1 a2..am)的前面

//此题有个最朴实的方法 就是再开一个数组将其按照b1 b1... bn a1 a2... am的顺序存储一遍
//再复制回原数组

//王道解法:感觉这道题逆置的解法比较常用 而且比上面一道题的逆置更有普适性
//先将a1 a2 .. am b1 b2 .. bn整个表逆序为bn bn-1 ..b1 am am-1  a1
//再在[0,n-1]区间内逆置为b1 b2... bn 在[n,m+n-1]区间内逆置为a1 a2 ..am
void Reverse(ElemType A[],int left,int right,int ArraySize) 
{//left表示待逆置区间的左端点 right表示待逆置区间的右端点 
	if(left>=right||right>=ArraySize) return ;//输入数据不合理 
	
	int mid=(left+right)/2;
	
	for(int i=0;i<=mid-left;i++)//因为左侧端点不一定为0 所以是left+i 
	{
		ElemType temp=A[left+i];
		A[left+i]=A[right-i];
		A[right-i]=temp;
	}
 }
 
void Exchange(ElemType A[],int 0,int m+n-1,int ArraySize)
{//先将整个区间逆置 再分别将小区间逆置 
	Reverse(A,0,m+n-1,ArraySize);
	Reverse(A,0,n-1,ArraySize);
	Reverse(A,n,m+n-1,ArraySize);
 } 

9、线性表(a1 a2...an)中的元素递增且按顺序存储在计算机内,设计一个算法,完成用最少时间在表中查找数值为x的元素,若找到,将其与后继元素值交换,若找不到,将其插入表中,并使表中元素仍递增有序

//此题有多种解法 解法一是看了y总的二分之后想到的 但是容易漏情况 解法二是王道上的
//解法一:
bool Find_Or_Insert(SqList &L,int x,int l,int r)//x 表示要查找/插入 元素;l、r分别代表左右端点 
{
	while(l<r)
	{
		int mid=l+r>>1;
		if(L.data[mid]>=x) r=mid;
		else l=mid+1;
	}
	
	if(L.data[l]==x)
	{
		printf("%d的位置为%d:",x,l+1);
		return true;
	}
	else
	{
		if(l>=L.length-1&&x>L.data[L.length-1])//当x是不存在于L中、但插入后是最大的元素时 直接将L.data[L.length]=x 
		{
			L.data[L.length]=x;
		}
		else//否则 移动元素 
		{
			for(int i=L.length-1;i>=l;i--)
			{
				L.data[i+1]=L.data[i];
			}
			L.data[l]=x;
			
		}
		
		L.length++;
		return true;
	}
	
 } 

解法二
//使用折半查找

void  Search_Or_Insert(ElemType A[],ElemType x)
{
	int low=0,high=n-1,mid;
	mid=(low+high)/2;
	
	while(low<=high)
	{
		mid=(low+high)/2;//确定中轴点 
		
		if(A[mid]==x) break;//找到 直接跳出 
		else if(A[mid]<x) low=mid+1;
		else high=mid-1;
	} 
	
	if(A[mid]==x&&mid!=n-1)//当找到x值 且mid不是最后一个元素 则与其后继元素交换 
	{
		temp=A[mid];
		A[mid]=A[mid+1];
		A[mid+1]=temp; 
	}
	
	if(low>high)//这里有些易错点 比如for循环里面为什么是i>high 这里最好代几个值推一下
	{
		for(int i=n-1;i>high;i--)
		{
			A[i+1]=A[i];//后移元素 
		}
		A[i+1]=x;//出循环后 i=high,但A[i+1]此时是空的 所以插入 
	}            
}

10、【2010】设将n(n>1)个整数存放到一位数组R中。设计一个在时间和空间两方面都尽可能高效的算法。将R中保存的序列循环左移p(0<p<n)个位置,即将R中的数据由(X0 X1 ..Xn-1)变为(Xp Xp+1...Xn-1 X0 ..Xp-1)。

//与上面一道题几乎相同
//先将前p个元素逆置 再将后面n-p个元素逆置
//最后再将整个区间逆置即可

void Reverse(ElemType A[],int left,int right)//left、right代表区间左右端点 更具代表性 
{
	int mid=left+right>>1;
	int temp;//做交换的临时变量
	
	for(int i=0;i<=mid-left;i++)
	{
		temp=A[left+i];
		A[left+i]=A[right-i];
		A[right-i]=temp;
	 } 
	
 } 

//王道解法 Reverse更简便 但原理相同
void Reverse(ElemType A[],int low,int high)
{
	int i,temp;
	
	for(i=0;i<(high-low+1)/2;i++)//取上整 
	{
		temp=A[low+i];
		A[low+i]=A[high-i];
		A[high-i]=temp;
	}
 } 

void Converse(ElemType A[])
{
	Reverse(A,0,p-1);//将前p个元素逆置 
	Reverse(A,P,n-1);//将后n-p个元素逆置 
	Reverse(A,0,n-1);//最后将n个元素逆置 
}

//时间复杂度为O(n) 空间复杂度为O(1)

// 解法二

//借助辅助数组
//将前p个元素存在另一个数组中 同时将后n-p个元素左移p
//最后将辅助数组中的元素复制到原数组后p个位置中去

//此解法的时间复杂度为O(n) 空间复杂度为O(p)

11、

//找出两个合并的升序序列的中位数

//直接想法:
//1、维护两个指针,分别指向两个升序序列,按照归并的写法将
//较小的数加入一个新数组中,最终新数组为所有元素的升序序列
//然后再取中位数即可
 int merge_and_found(int n,int A[],int B[],int C[])//A B等长 
 {
 	int i,j,k;//i j分别是遍历A[]和B[]的指针 
	for(i=0,j=0;i<n&&j<n;k++)
	{
		if(A[i]<B[i]) C[k]=A[i++];
		else C[k]=B[j++];
	}
	while(i<n) C[k++]=A[i++];
	while(j<n) C[k++]=B[j++];
	
	return C[k+1>>1];
	
} 
//时间复杂度O(n) 空间复杂度O(n) 


//二:归并进行判断 但是不排序 指针扫到一半则停止 
int found2(int n,int A[],int B[])
{
	int i=0,j=0;
	int cnt=0;//用于计数  当cnt扫描至两个序列的一半时 输出该元素 
	
	while(cnt<2*n)
	{
		if(A[i]<B[j])
		{
			i++,cnt++;
			if(cnt==(2*n)>>1) return A[i-1];//将坐标恢复 
			
		} 
		else
		{
			j++,cnt++;
			if(cnt==(2*n)>>1) return B[j-1];//将坐标恢复	
		} 
	}
 } 
//时间复杂度为O(n) 空间复杂度为O(1)

//官方解法
//先分别求A B两个序列的中位数 记为a b 然后比较
//若a==b 则找到所求 结束
//若a<b 则舍弃序列A中较小的一半,同时舍弃序列B中较大的一半(要求舍去元素个数相等 这样原始中位数的位置不变)
//若a>b 则舍弃A中J较大的一半,同时舍弃B中较小的一半(舍去元素个数相等)
//在新的序列中 重复上述过程 直至两个序列均只含一个元素位置 选择较小的元素输出 
int M_search(int A[],int B[],int n)
{
	int st1=0,ed1=n-1,st2=0,ed2=n-1;//st表示起始 ed表示结束
	int mid1,mid2;
	while(st1!=ed1||st2!=ed2)//只有两个序列都是一个元素时才出循环 
	{
		mid1=st1+ed1>>1;
		mid2=st2+ed2>>1;
		if(A[mid1]==B[mid2])
		{
			return A[mid1];
		 } 
		if(A[mid1]<B[mid2])
		{
			if((st1+ed1)%2==0)//A中元素个数为奇数 
			{
				st1=mid1;//舍弃A中间点以前的部分且保留中间点 
				ed2=mid2;//舍弃B中间点以后的部分且保留中间点 
			}else//A中元素为偶数 
			{
				st1=mid1+1;//舍弃A中间点以前的元素包括中间点 
				ed2=mid2;//舍弃B中间点以后部分但保留中间点 
			}
		}else//A[mid1]>=B[mid2] 
		{
			if((st2+ed2)%2==0)//B中元素个数为奇数 
			{
				ed1=mid1;//舍弃A中间点以后的部分且保留中间点 
				st2=mid2;//舍弃B中间点以前的部分且保留中间点 
			}else//A中元素个数为偶数 
			{
				ed1=mid1;//舍弃A中间点以后的部分 保留中间点 
				st2=mid2+1;//舍弃B中间点以前的部分包括中间点 
			} 
		} 
		
	} 
	
	//出while时 st1==ed1  st2==ed2 
	return A[st1]<B[st2]?A[st1]:B[st2];
}
//时间复杂度A(logn) 空间复杂度O(1)

12、

//第一种朴素想法 空间换时间 开一个数组用于计数 记录扫描过程中某个元素出现的次数 
//当某个元素出现的次数大于整个数组元素个数的一半时 则说明找到主元素
const int N=100010;
int s[N];//用于计数 

//找主元素
int Find(int A[],int n)//n代表A中元素个数 
{
	for(int i=0;i<n;i++)
	{
		s[A[i]]++;
		if(s[A[i]]>(n>>1))//当某一个元素的次数大于整个数组元素个数的一半时 则为主元素 
		{
			return A[i];
		}
	}
	
	return -1;//未找到主元素 输出-1 
	
 } 
//时间复杂度为O(n) 空间复杂度也是O(n)

//王道解法
int Majority(int A[],int n)
{
	int i,c,count=1;//i用作扫描数组的指针 c用于保存候选主元素 count用于计数
	c=A[0];//将第一个元素视为候选主元素
	for(i=1;i<n;i++)
	{
		if(A[i]==c) 
			count++;//若扫描的元素等于候选主元素 则count+1 
		else//扫描的元素不等于候选主元素主元素 
		{
			if(count>0)//当count技术支持大于0时 将其-1 
				count--;
			else//count计数值为0 应该更换候选主元素 
			{
				c=A[i];
				count=1; 
			}
		 } 
	}//结束while后 c一定是可能成为主元素的元素 
	
	//再扫描一遍数组 统计c出现的次数
	for(i=0,count=0;i<n;i++)
	{
		if(A[i]==c)
			count++;
	} 
	if(count>n/2)//找到主元素 返回 
		return c;
	
	return -1;//未找到主元素 返回-1 
 } 

13、

#include <iostream>
#include <string.h> 

using namespace std;

const int N=10010;

//找出未出现的最小整数
//要求: 时间上尽可能高效----空间换时间
//思路: 扫描一遍原数组  记录下出现的正整数 将其放在一个新数组中(新数组赋初值为0) 
//再扫描一遍新数组  找第一个其值为0的下标(表示该元素在数组中未出现) 返回 

int A[N];

int findMissMin(int A[],int n)
{
	int i=0;
	int *B;//开辟新数组 用于记录原数组中的值是否出现
	B=new int[n];//下标从0开始 建立0~n-1 到1~n的映射
	
	//注意memset赋值 要加上头文件 而且memset是给字节赋值 当传递参数是数组时 要传数组的长度 
	memset(B,0,sizeof(int)*n);//给B赋初值为0 表示起初没有一个元素出现 
	
	for(i=0;i<n;i++)
		if(A[i]>0&&A[i]<=n)//大于n的值不必记录 因为该值不可能是要求的值 
	 		B[A[i]-1]=1;//A中的元素n实际对应B中为n-1的记录 
	for(i=0;i<n;i++)
		if(B[i]==0) break;
	return i+1;
}

int main()
{
	int n;
	cin>>n;
	
	cout<<"输入元素:";
	for(int i=0;i<n;i++)
		cin>>A[i];
//	int len=sizeof(A);
//	cout<<len<<endl; 
	cout<<"A中元素为:";
	for(int i=0;i<n;i++) cout<<A[i]<<" ";
	cout<<endl;
	
	int res=findMissMin(A,n);
	cout<<"未出现的最小正整数为:"<<res<<endl; 
	
}

14、

#include <iostream>
using namespace std;

#define INT_Dis 0x7FFFFFFF //int的最大值 

const int N=10010;
int A[N],B[N],C[N];

//思路:
//只有改变三个元素中的最小值才可能使得总距离最小 
//首先 假设一个最小值(设为无穷大) 当计算所得的结果大于该最小值则更新最小值
//更新的是时候每次都更新三个元素中的最小值 让其坐在集合的坐标+1

//计算绝对值
int abs_(int a)
{
	return (a>0)?a:(-a);
 } 
 
//找三个元素的最小值
bool min(int a,int b,int c)
{
 	if(a<=b&&a<=c)
 		return true;
 	else
 		return false;
} 

//找最小距离
int findMin_Dis(int A[],int m,int B[],int n,int C[],int p)
{//m n p分别代表三个升序数组的元素个数
	int D_min=INT_Dis,D;
	int i=0,j=0,k=0;//分别表示三个数组的下标 
	while(i<m&&j<n&&k<p&&D_min>0)
	{
		D=abs_(A[i]-B[j])+abs_(B[j]-C[k])+abs_(C[k]-A[i]);
		if(D<D_min) D_min=D;//更新最小距离
		//更新最小元素值的下标 
		if(min(A[i],B[j],C[k]))
			i++;
		else if(min(B[j],A[i],C[k]))
			j++;
		else
			k++; 
	}
	
	return D_min;
 } 
 

int main()
{
	int m,n,p;//代表三个升序数组的元素个数
	cout<<"输入三个升序数组维度:"<<endl;
	cin>>m>>n>>p; 
	//输入数组A 
	cout<<"输入数组A:";
	for(int i=0;i<m;i++) cin>>A[i];
	//输入数组B
	cout<<"输入数组B:";
	for(int i=0;i<n;i++) cin>>B[i];
	//输入数组C
	cout<<"输入数组C:";
	for(int i=0;i<p;i++) cin>>C[i];
	
	int res=findMin_Dis(A,m,B,n,C,p); 
	
	cout<<"最小距离为:"<<res<<endl;
	
	
	return 0;
}

2-2

1、设计一个递归算法,删除不带头结点的单链表L中所有值为x的结点

 //删除不带头结点的单链表中所有值为x的值---递归算法 
 bool Dele_x(LinkList &L,ElemType x)
 {
 	LNode *p;//临时结点 
 	
 	//注意不带头结点 判空为 L==NULL 
 	if(L==NULL) return false;//链表为空 返回false 
 	
 	if(L->data==x)//当数据域 是要删除的值时 
 	{
 		p=L;
 		L=L->next;//修改头指针 指向下一个结点
 		free(p);
 		Dele_x(L,x);
	}else
	{
		Dele_x(L->next,x);
	}
	
	return true;
 	
  } 

2、在带头结点的单链表中,删除所有值为x的结点,并释放其空间,假设值为x的结点不唯一,编写算法

//这个题用第一题的递归写法应该也可以做 这里提供王道的 另一种写法
bool Dele_x1(LinkList &L,ElemType x)
{
	//带头结点 
	if(L->next==NUll) return false;//表为空 返回false 
	
	LNode *p=L->next,*prev=L,*q;//维护三个节点 p初始为首结点 prev初始为头指针 q为临时结点 
	while(p!=NULL)
	{
		if(p->data==x)//当首节点的值是目标值时 
		{
			q=p;
			p=p->next;//断链 
			prev->next=p;
			free(q);
		}else//否则 p后移 同时prev也保持p的前驱结点关系 
		{
			prev=p;
			p=p->next;
		}
	}
    return ture;
 } 

//用尾插法建立 当元素不为x时 则插入尾部 
void Dele_2(LinkList &L,ElemType x)
{
	LNode *p=L->next,*r=L,*q;//r表示尾指针 q表示临时结点 
	
	while(p!=NULL)
	{
		if(p->data!=x)//当值不为x时 链接在链尾 
		{
			r->next=p;
			r=p;
			p=p->next;
		}else
		{
			q=p;
			p=p->next;
			free(q);
		}
	}
	
	r->next=NULL;//将尾指针置空 
 } 

3、设L为带头结点的单链表,编写算法实现从尾到头反向输出每个结点的 值。

参考思路:王道  用栈的思想 即递归的方法来实现

//反向输出
void R_print(LinkList L)
{
	if(L->next!=NULL)
	{
		R_print(L->next);//递归 
	}
	if(L!=NULL) printf("%d ",L->data);//先输出尾结点元素 再递归输出其他元素
 }

//调用时 R_print(L->next); 从第一个元素节点开始 

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

//带头结点的单链表中删除一个最小值结点的高效算法
//思路:维护两个指针 一个指向当前结点 另一个指向当前节点的前驱结点
//再维护两个指针 一个用于指向最小权值结点的指针 一个用于指向最小权值结点指针的前驱结点 
//逐个比较每一结点的权值 若扫描到的结点的权值比当前及结点的权值小
//则修改最小指针以及最小指针结点的前驱结点 
void Delete_Min(LinkList &L)
{
	LinkNode *pre=L,*p=pre->next;
	LinkNode *minp=p,*minpre=pre;//初始化最小权值结点为首元素结点 
	while(p!=NULL)
	{
		if(p->data<minp->data)
		{
			minp=p;//修改最小结点指针的指向 
			minpre=pre;//同时修改最小权值结点的前驱结点 
		}
		
		pre=p;
		p=p->next;
	}
	
	//结束循环后一定找到了最小元素权值结点
	minpre->next=minp->next;//修改指针 
	free(minp);
}

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

//就地逆转单链表
//1、头插法的思想
LinkList Reverse_1(LinkList L)
{
	LNode *p,*r;//p指向当前结点 r指向p的后继结点
	p=L->next;//处理首元素结点
	L->next=NULL;
	while(p!=NULL)
	{
		r=p->next;//保存p的后继结点
		p->next=L->next;//插入到头结点之后 
		L->next=p;
		p=r;//修改p指针 进行下一个结点的逆置 
	}
	
	return L; 
 }
 
//2、指针反转
LinkList Reverse_2(LinkList L)
{
	LNode *pre,*p,*r;
	p=L->next,r=p->next;//p代表第一个元素结点 r是p的后继结点
	p->next=NULL;//将第一个元素结点的指针域置空 此时p与r之间的链断开 
	while(r!=NULL)
	{
		pre=p;
		p=r;
		r=r->next;//找到下一个后继结点 
		p->next=pre;//指针反转 
	}
	
	//出循环时 r为NULL p是最后一个元素结点 
	L->next=p;//将L的next域指向最后一个元素结点 则最后一个元素结点作为首元素结点
	
	return L; 
}

6、有一个带头结点的单链表L,设计一个算法使其元素递增有序

//直接想法 空间换时间
//1、开一个数组 将链表中元素复制到数组中
//2、将数组中元素排序(升序)
//3、 排序完成后 再将数组中元素插入链表中

//王道解法
//扫描单链表 寻找元素结点合适的插入位置 
void Sort(LinkList L)
{
	LNode *pre,*p,*r;//p指向待处理元素的第一个结点 r指向p的后继结点
	p=L->next;
	r=p->next;//存储下一个结点
	p->next=NULL;//断链  此时只含一个数据结点 
	p=r;//指向r  
	while(p!=NULL) 
	{
		r=p->next;
		pre=L;
		
		while(pre->next!=NULL&&pre->next->data<p->data)
			pre=pre->next;//用于寻找合适的插入位置
		//出循环有两种情况
		//1、pre==NULL 说明已排好序的元素中 p结点的权值是最大的 则将其插入尾部
		//2、pre->next->data>=p->data 说明此时pre的结点的权值是小于p结点的 但下一个结点的权值大于p结点
		//则p结点应插在pre之后 
		
		p->next=pre->next;//将该元素结点插入pre后(该元素结点的正确位置) 
		pre->next=p;
		p=r;//修改p指针 持续进行 
	}
}

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

//删除位于给定两个元素之间的值的结点
void Delete(LinkList &L,int l,int r)
{
	LNode *pre,*p;//p用于扫描整个单链表 pre指向其前驱
	pre=L,p=L->next;//初始化pre 和 p 
	while(p!=NULL)
	{
		if(p->data>l&&p->data<r)//删除 
		{
			pre->next=p->next;
			free(p);
			p=pre->next;//修改p指针 
		}else//修改pre 和 p指针 继续向后扫描 
		{
			pre=p;
			p=p->next;
		} 
	}
 }

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

//直接想法 
//两个while循环 
//以其中第一个单链表的首元素结点为始 遍历另一个单链表中 找到相同权值的元素即输出
//然后再移动第一个单链表的结点指向下一个结点 再遍历整个链表
//时间复杂度 为 O(len1*len2)

//王道解法
//找两个链表的公共结点
//注:公共结点是指指针相同 而不是数据域相同
//即从公共结点后两个链表完全重合

//思路:因为是公共链表 所以尾部一定是对齐的
//先判断长度 让更长的链表移到与更短的链表对齐 
LinkList Find_Commom(LinkList L1,LinkList L2)
{
	int len1,len2,dist;//存储两个链表的长度  dist表示两个链表的长度差值 
	LinkList LongerList,ShorterList;//分别表示两个不同长度的链表
	len1=Length(L1),len2=(Length L2);//计算两个链表的长度
	if(len1>len2)
	{
		LongerList=L1->next;
		ShorterList=L2->next;
		dist=len1-len2;
	}else
	{
		LongerList=L2->next;
		ShorterList=L1->next;
		dist=len2-len1;
	} 
	
	while(dist--)
		LongerList=LongerList->next;//让长链表移动到与短链表头同步的位置 
	
	while(LongerList!=NULL)
	{
		if(LongerList==ShorterList)
			return LongerList;//找到公共结点
		else//第一个结点不是公共结点 同步往后移动 
		{
			LongerList=LongerList->next;
			ShorterList=ShorterList->next; 
		} 
	}
	
	return NULL;//没有公共结点返回NULL 
 } 
//时间复杂度 O(len1+len2)

9、

//思路:每次找单链表结点的最小权值的结点
//然后修改指针 并将其free 再找余下的结点中具有最小权值的结点 
 
void Print_IncreaseOrder(LinkList &head)
{
	LNode *p,*pre,*q;//p指向最小权值的结点 pre指向其前驱结点  q暂存最小权值的结点 
	p=head->next,pre=head;
	
	while(head->next!=NULL)
	{
		while(p->next!=NULL)
		{
			if(p->next->data<pre->next->data)//说明p的后继结点比p的权值小 即p不是最小权值的结点 
				pre=p;//修改pre指针 使pre指向当前最小权值结点的前驱结点 
			p=p->next;//若p是当前具有最小权值的结点 则向后扫描 
		}
		//出while后pre一定是最小权值结点的前驱结点  
		print(pre->next->data);
		
		q=pre->next;
		pre->next=q->next;
		free(q);
	}
	free(head);//输出完毕 释放头指针 
}

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

//思路:第一个结点保存在A中 下一个结点保存在B中同时删除该结点
 
LinkList ReadjustLinkList(LinkList &A)
{
	int i=0;//记录奇偶数
	LinkList B=(LinkList)malloc(sizeof(LNode));
	B->next=NULL;//初始化B
	LNode *s=(LNode *)malloc(sizeof(LNode)),*rb=B;//s用来存A中的偶数结点 r代表尾指针用尾插法插入 B
	
	LNode *p=A->next,*ra=A;//p为工作指针  从首元素结点开始
	A->next=NULL;//让A断开 进行尾插 
	while(p!=NULL)
	{
		i++;
		if(i&1==1)//奇数 则不用对A的结点做任何处置 
		{
			ra->next=p;
			ra=p;//尾插法插入A中 
		}	
		else//偶数结点 
		{
			s->data=p->data;//将偶数结点尾插入B 
			r->next=s;
			r=s;
		}
		p=p->next;//工作指针向后扫描 
	}
	ra->next=NULL;
	rb->next=NULL;
	
	return B; 
	 
 }

11、

//与上题思路相同 B表采用头插法即可

LinkList ReadjusstLinkList(LinkList &A)
{
	LinkList B=(LinkList)malloc(sizeof(LNode));
	B->next=NULL;
	LNode *p=A->next,*ra=A;
	A->next=NULL;
	while(p!=NULL)
	{
		ra->next=p;//将第一个元素尾插入A 
		ra=p;//修改尾指针
		p=p->next;
		if(p!=NULL)
		{//头插法插入B 
			LNode *q=p->next;//保存p的后一个结点 下面操作可能会断链 
			p->next=B->next;
			B->next=p;
			p=q;//将新结点赋给p 
		} 
	}
	ra->next=NULL;//将A的尾指针置为NULL 
	return B; 
 } 

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

//删除递增有序单链表中的重复元素
void Delete_Same(LinkList &L)
{
	LNode *prior,*p=L->next;//p代表工作指针 prior为其前驱指针
	if(p==NULL)
		return ;
	while(p!=NULL)
	{
		prior=p;
		p=p->next;
		if(p->data==prior->data)//如果元素值相同 
		{//修改指针 
			prior->next=p->next;
		} 
	} 
 } 

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

//将两个顺序单链表 合并 成一个逆序单链表
//要求:要用到两个原表的结点

//思路:利用头插法逆序 与归并排序操作类似 

void MergeList(LinkList &La,LinkList Lb)
{//La 用来存放最终合并后的单链表
	LNode *r,*pa=La->next,*pb=Lb->next;//r为工作指针 
	
	//将La断链 以便头插
	La->next=NULL;
	
	while(pa&&pb)
	{
		if(pa->data<pb->data)
		{//pa指向的元素权值小于pb指向的元素权值 
			r=pa->next;//先将pa的后一结点保存 后面将修改pa
			
			//头插 
			pa->next=La->next;
			La->next=pa;
			 
			pa=r;//保持pa不断 
		}else
		{
			r=pb->next;
			
			pb->next=La->next;
			La->next=pb;
			
			pb=r;
		} 
	} 
	
	//最终一定有一个表有剩余元素 
	if(pa)//处理pa 
	{
		pb=pa;//放到pb中处理 
	}
	
	if(pb)//pb有剩 
	{
		while(pb)
		{
			r=pb->next;
			
			pb->next=La->next;
			La->next=pb;
			
			pb=r;
		}
	} 
	
	//归并结束 销除Lb
	free(Lb); 
} 

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

//在两个递增有序链表中产生公共元素 用一新链表接收
//思路:设置两个指针 分别从两个链表起始比较 
//若权值相同 则插入C 若A中元素更大 则让B中指针指向下一个元素 反之 则让A中指针指向下一个元素 

void CommonElement(LinkList A,LinkList B,LinkList &C)
{
	LNode *p=A->next,*q=A->next;//p q表示两个工作指针 
	C=(LinkList)malloc(sizeof(LNode))
	LNode *r=C,*s;//r代表尾插法插入C的尾指针 s用于接收共同元素结点

	while(p&&q)
	{
		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 q指针
			p=p->next;
			q=q->next; 
		}
	}
	r->next=NULL;
}

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

//此题与上题类似 但王道用的归并的方法 但思路是相同的
void Union(LinkList &La,LinkList &Lb)
{
	LNode *pa=La->next,*pb=Lb->next;//工作指针
	La->next=NULL;
	LNode *pc=La,*u;//u是临时结点 
	while(pa&&pb)
	{
		if(pa->data==pb->data)
		{//权值相同 两个指针同时后移 
			pc->next=pa;
			pc=pa;
			pa=pa->next;
			u=pb;
			pb=pb->next;
			free(u);//Lb中结点释放 
		}else if(pa->data>pb->data)
		{//pa权值更大 让pb指针后移 
			u=pb;
			pb=pb->next;
			free(u);
		}else
		{//pa指针后移 
			u=pa;
			pa=pa->next;
			free(u);
		} 
	} 
	
	//两个单链表有剩
	while(pa)
	{//pa有剩
		u=pa;
		pa=pa->next;
		free(u); 
	} 
	
	while(pb)
	{//pb有剩 
		u=pb;
		pb=pb->next;
		free(u);
	}
	
	pc->next=NULL;
	free(Lb);
} 

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

//16、判断B是否是A的连续子序列

//王道解法 -类似于字符串匹配思想 
int Pattern(LinkList A,LinkList B)
{
	LNode *p=A->next;
	LNode *pre=p;//pre记住每次比较中A链表的开始结点 
	LNode *q=B->next;
	
	while(p&&q)
	{
		//若匹配成功 则每次同步更新A B的下一个元素 
		if(p->data==q->data)
		{
			p=p->next;
			q=q->next;
		}
		
		//若匹配不成功 更改A指针指向上一轮开始结点的下一个节点 q每次回溯到链表首元素 继续比对 
		pre=pre->next;
		p=p->next;
		q=B->next;
	}
	if(q==NULL)
	{//B序列比较结束 说明B是A的子序列 
		return 1;
	}else
	{
		return 0;
	}
	 
 }

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

//17、判断循环双链表是否对称

bool Judge_DLinkSymmetry(DLinkList L)
{
	DNode *p=L->next,*q=L->prior;//p指向首元素结点 q指向尾元素结点
	while(p!=q&&p->next==q)//循环跳出 
	{
		if(p->data!=q->data)
			return false;//不是中心对称 
		else
		{//若两者相等 则让p、q指向下一个结点 持续比较 
			p=p->next;
			q=q->prior;
		}
	} 
	
	return true;
}

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


//18、将循环单链表h2链接到循环单链表h1之后
//思路:先找到两个循环单链表的尾结点 之后修改结点即可 
void Link_h2Toh1(LinkList &h1,LinkList h2)
{
	LNode *p,*q;//p 和 q分别表示L1和L2的工作指针
	p=h1,q=h2;
	while(p->next!=h1)//找到h1的尾结点 
		p=p->next;
	while(q->next!=h2)//找到h2的尾结点 
		q=q->next; 
	//修改h1的尾结点指针指向h2 h2的尾结点指针指向h1
	p->next=h2;
	q->next=h1; 
} 

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

void findMinAndDelete(LinkList &L)
{
	LNode *p,*pre,*min,*minpre;//p--工作指针 pre--p的前驱指针 min--最小结点指针 minpre--最小结点指针的前驱指针
	
	while(L->next!=L)//一直循环到L为空 
	{//每循环一次找到并输出一个最小值 
		p=L->next,pre=L,min=p,minpre=pre;
		while(p->next!=L)//找出最小值结点 
		{
			if(min->data>p->data)
			{
				//修改最小值结点指针
				min=p;
				minpre=pre; 
			}else
			{
				//往后扫描
				pre=p;
				p=p->next 
			}	
		}
		//出第二个while后一定找到了此时链表内的最小值结点
		printf("%d",min->data);
		//修改指针 删除最小值结点
		minpre->next=min->next;
		free(min); 
	}
	
	//出外while后 必然L内元素为空
	free(L); 
}
//此题做法对单链表一样适用 只是把while内条件修改为L->next!=NULL 以及p->next!=NULL

20、

//定位元素结点 并按频度域排序 
LNode* Locate(LinkList &L,int x)
{
	LNode *p=L->next;//p为工作指针 用于定位元素值为x的结点 
	LNode *q=L->next,*qpre=L;//q表示寻找插入位置的指针 qpre指向待插入位置的前驱 
	LNode *ppre,*pnext;//分别代表定位结点的前驱结点 以及后继结点 
	
	//先找到插入的位置 
	while(p->data!=x)
	{
		p=p->next;
	}
	
	if(!p)//p循环到链尾NULL 说明没有找到该权值的结点 
	{
		return NULL;
	}else//存在该权值的结点 
	{
		//该结点频度域+1
		int fre=++p->freq; 
		ppre=p->pred;
		pnext=p->next;
		p->next=NULL,p->pred=NULL;
		//将该结点摘除 与其之前的结点的频度域比较 插在适合的位置
		
		//printf("此时首元素结点为:%d\n",q->data);
		//找到要该结点插入的位置 
		while(fre<q->freq)
		{
			qpre=q;
			q=q->next;
		}
		//修改指针指向
		p->next=qpre->next;
		qpre->next=p;
		q->pred=p;
		p->pred=qpre;
		 
		if(!pnext)//p是最后一个节点 则直接将ppre指向NULL 
			ppre->next=NULL;
		else//p不是最后一个节点 将链表连接 
		{
			ppre->next=pnext;
			pnext->pred=ppre;
		}
		return p;
	}
} 

//优化--由于是双向链表 可以直接从p的pred结点开始寻找

LNode* Locate(LinkList &L,int x)
{
	LNode *p=L->next,*q;//p为工作指针 q为p的前驱
	while(p&&p->data!=x)
		p=p->next;
	if(!p)
		exit(0);
	else{
		p->freq++;
		if(p->pred==L||p->freq<p->pred->freq)
			return p;//p是第一个结点 或者p的前驱结点的频度域严格大于p的频度域 直接返回 
		if(p->next!=NULL)
			p->next->pred=p->pred;//将p从链表摘下
		p->pred->next=p->next;
		q=p->pred;
		while(q!=L&&q->pred<=p->pred)
			q=q->pred;//从p向前找到第一个大于p的频度域的结点
		
		//修改指针 
		p->next=q->next;
		if(q->next!=NULL) q->next->pred=p;//让p成为相同频度域的第一个结点 
		p->pred=q;
		q->next=p; 
	} 
	return p;
 } 

21、

//21、判断单链表有环
//思路:使用快慢指针 
//两个指针起初都指向head 快指针每次走两步 慢指针每次走一步 当快指针等于慢指针时则说明有环
//否则 快慢指针都会走到最后为NULL

bool HaveRing(LNode *head)
{
	LNode *fast=head,*slow=head;
	while(fast=NULL&&fast->next!=NULL)
	{
		slow=slow->next;
		fast=fast->next->next;
		if(fast==slow)
			return true;
	}
	
	return false;
} 

//若要找到环的入口
LNode *FindLoopStart(LNode *head)
{
	LNode *fast=head,*slow=head;
	while(fast=NULL&&fast->next!=NULL)
	{
		slow=slow->next;
		fast=fast->next->next;
		if(fast==slow)
			break;
	}
	
	if(slow==NULL||fast->next==NULL)
		return NULL;//无环 
	LNode *p1=head,*p2=slow;//让p1指向开始点 p2指向相遇点 
	while(p1!=p2)
	{
		p1=p1->next;
		p2=p2->next;
	}
	
	return p1;//返回入口点 
 } 

22、

//22、输出链表中倒数第k个位置上的结点

//直接思路:
//开一个数组 顺序扫描链表 将每个结点的值依序赋值到数组中 同时设置一个计数器统计链表长度L
//访问倒数第k个元素结点 即是访问正序第L-k+1个结点 
//当k<=L时 说明存在这个结点 通过数组直接访问A[L-k+1] k>L则不存在这个结点
//时间复杂度为O(n)(扫描O(n)+访问O(1)) 空间复杂度为O(n)

//高效解法
//思路:设置两个指针p q 初始时均指向首元素结点
//起初 p指针沿链表移动 q指针保持不动 当p移动到第k个结点时 p和q同步移动
//则当p移动到链表尾部时 q指针就移到倒数第k个结点

int Search_K(LinkList list,int k)
{
	LNode *p=list->next,*q=list->next;
	int cnt=0;//用于与k比较 
	
	while(p!=NULL)
	{
		if(cnt<k)//当cnt<k时 只移动p指针 
			cnt++;
		else//当cnt==k时 同时移动指针p和指针q 
			q=q->next;
		p=p->next;
	}
	
	if(cnt<k)
	{//表明循环完了所有结点 但是结点个数仍小于k 则不存在要找的结点 
		return 0;
	} 
	
	printf("%d",q->data);
	return 1;
	
}

 23、

//23、题意:已经给定了两个拥有共同后缀的字符序列 只需判断地址(即指针)相同即可 不必判断结点内的字符
//思路:首先让两个链表 对齐 
//设置两个指针 同步扫描 直到找到相同的地址 即:两个指针指向的下一个结点相同

int length(LinkList L)
{//求链表长度 以便于将更长的链表移到与更短的链表长度相同的结点处同步比较 
	int len=0;
	while(L->next!=NULL)
	{
		len++;
	 	L=L->next;
	}
	 
	return len;
 } 

LNode* find_ComAddr(LinkList str1,LinkList str2)
{
	int len1=length(str1),len2=length(str2);
	LNode *p=str1->next,*q=str2->next;//p q为两个工作指针 初始指向首元素 
	
	if(len1>len2)
	{//str1长度大于str2长度 将str1移动到与str2长度相同处
		for(int i=len1;len1>len2;len1--)
			p=p->next;
	}
	
	if(len1<len2)
	{//str2长度大于str1长度 将str2移动到与str1长度相同处
		for(int i=len2;len2>len1;len2--)
			q=q->next;
	}
	
	//两链表均同步后 同步后移
	while(p->next!=NULL&&p->next!=q->next)
	{//当p和q的下一个结点相同时 跳出while 
		p=p->next;
		q=q->next;
	}
	
	return p->next; 
	
} 
//时间复杂度为 O(len1+len2) 或 O(max(len1,len2)) 

24、

 

//24--空间换时间
//维护一个数组 维度为n+1 (因为|data|<=n) 初始为0
//顺序扫描链表 若每个元素是第一次出现(数组中值为0)则保留 否则删除

void Delete_Sameabs(LinkList &head,int n)
{
	LNode *p=head->next,*pre=head,*r;
	int *q,val;//*q代表数组 
	q=(int *)malloc(sizeof(int)*(n+1));
	
	//数组内元素初始化
	for(int i=0;i<n+1;i++)
		*(q+i)=0;
	while(p!=NULL)
	{
		val=p->data>0?p->data:(-p->data);
		if(*(q+val)==0)
		{//val是第一次出现 保留 
			*(q+val)=1;
			pre=p;
			p=p->next
		}else
		{//val重复出现 删除 
			r=p;
			pre->next=p->next;
            p=p->next;//p指针指向后一节点
			free(r);
		}
		
	} 
	free(q);
}

25、

//25、调整单链表

//思路:先将单链表的后半段结点逆置 为此需找到单链表的中间结点 
//设置两个指针p q, p每次移动一步 q每次移动两步 当q移动到链表尾时 p移动到链表中点
//将后半段链表逆置后 再按要求将元素插入链表即可---蕴含头插法的思想

void Readjust_List(LNode* head)
{
	LNode *p=head,*q=head;//用于找到链表中点 初始时都指向头指针 
	LNode *r,*s;//r用于逆置后半段链表 保证链不断   s用于前半段链表的调整 

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

	
	q=p->next;//q是后半段链表的第一个节点
	p->next=NULL;//从p处断开 
	while(q!=NULL)
	{
		r=q->next;//记录q的后一个结点 保证不断链 
		q->next=p->next;//从中点开始头插 使后半断链表逆置
		p->next=q;
		q=r; 
	}

	s=head->next;
	q=p->next;
	p->next=NULL;
	while(q!=NULL)
	{
		r=q->next;//r指向后半段的下一个插入点 
		q->next=s->next;
		s->next=q;
		s=q->next;//s指向前半段的下一个插入点 
		q=r;
	}
 }

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值