《数据结构》上机实验(第二章)——线性表 Ⅴ

参考书目:《2022年 数据结构 考研复习指导》王道论坛 组编
在这里插入图片描述
在这里插入图片描述

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

  • 算法思想:搜索整个顺序表,查找最小值元素并记住其位置,搜索结束后用最后一个元素填补空出的原最小值元素的位置。
bool DelEle(SqList &L, int &min) //形参为引用类型
{
	int i, j;
	min = L.data[0]; //假设第一个元素为最小值
	if (L.length == 0) //如果线性表为空
	{
		cout<<"The Sequence List is Empty!"<<endl; //输出错误信息
		return false; //退出运行
	}
	 //如果线性表不为空
	for (i = 1; i < L.length-1; i++) //从第二个元素到线性表中的最后一个元素
		if (L.data[i] < min) //如果后面出现比第一个元素小的值
		{
			min = L.data[i]; //将当前值设为最小值
			j = i; //记录当前值元素的下标
		}
	L.data[j] = L.data[L.length - 1]; //空出的位置由最后一个元素填补
	L.length--; //线性表当前长度-1
	return true; //退出运行
}

程序分析:

  • 函数需要使用布尔类型做判断,因此使用C++编写。
  • 运行结果:
    在这里插入图片描述
  • 时间复杂度:O(n);空间复杂度:O(1)

2. 将顺序表L的所有元素逆置。

  • 算法思想:扫描顺序表L的前半部分元素,对于元素 L . d a t a [ i ] ( 0 ≤ i < L . l e n g t h 2 ) L.data[i](0≤i<\frac{L.length}{2}) L.data[i](0i<2L.length),将其与后半部分的对应元素 L . d a t a [ L . l e n g t h − i − 1 ] L.data[L.length-i-1] L.data[L.lengthi1]进行交换。
void DiverseElem(SqList &L)
{
	int i, t;
	for (i = 0; i < L.length / 2; i++) //循环到线性表中间结束
	{ 
		t = L.data[i];
		L.data[i] = L.data[L.length - 1 - i];
		L.data[L.length - 1 - i] = t;
	} //第一个元素与最后一个元素交换;第二个元素与倒数第二个元素交换...
}

程序分析:

  • 运行结果:
    在这里插入图片描述
  • 时间复杂度: O ( n 2 ) O(\frac{n}{2}) O(2n);空间复杂度:O(1)

3. 对长度为n的顺序表L,删除线性表中所有值为x的数据元素。

参考链接中对应的第四题,该题有两种解法。
点击!

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

算法思想:先寻找值≥s的第一个元素(第一个删除的元素),然后寻找值>t的第一个元素(最后一个删除的元素的下一个元素),要将这段元素删除,只需直接将后面的元素前移。

bool DelEle(SqList &L, int s, int t)
{
	int i, j;
	if (s >= t || L.length == 0) //如果输入的s>t或者线性表为空
	{
		printf("data error!"); //显示出错信息
		return false;
	}
	for (i = 0; i < L.length && L.data[i] < s; i++); //循环找出大于等于s的第一个元素
	if (i >= L.length) //如果线性表中每一个元素的值都小于s
	{
		printf("No element delete.\n"); //则没有元素需要删除
		return false;
	}
	for (j = i; j < L.length && L.data[j] < t; j++); //循环找出大于t的第一个元素
	for(;j<L.length;j++,i++)
		L.data[i] = L.data[j];
	L.length = L.length - (j - i);
	return true;
}
DelEle(L,4,6);

程序分析:

  • !注意: 本题与上一题存在区别。因为是有序表,所以删除的元素必然是相连的整体。
  • 运行结果:
    在这里插入图片描述
  • 时间复杂度:O(n);空间复杂度:O(1)

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

算法思想:从前向后扫描顺序表L,用count记录下元素值在s到t之间元素的个数(初始时count=0)。对于当前扫描的元素,若其值不在s到t之间,则前移count个位置;否则执行count++。由于这样每个不在s到t之间的元素仅移动一次,因此算法效率高。

参考链接中对应第四题的解法二。
点击!

bool DelEle(SqList& L, int s, int t)
{
	int i, count = 0; //count用来统计已删除的元素个数
	if (s >= t || L.length == 0) //如果输入的s>t或者线性表为空
		return false; //退出运行
	for (i = 0; i < L.length; i++)
	{
		if (L.data[i] >= s && L.data[i] <= t) //如果该元素在[s,t]范围内
			count++; //计数值+1
		else
			L.data[i - count] = L.data[i]; //否则将其他元素前移
	}
	L.length -= count; //删除元素后线性表的当前长度
	return true;
}
DelEle(L, 3, 7);

程序分析:

  • 函数需要使用布尔类型做判断,因此使用C++编写。
  • 运行结果:
    在这里插入图片描述
  • 时间复杂度:O(n);空间复杂度:O(1)

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

  • 算法思想:初始时将第一个元素视为非重复的有序表。之后依次判断后面的元素是否与前面非重复有序表的最后一个元素相同,若相同,则继续向后判断;若不同,则插入前面的非重复有序表的最后,直至判断到表尾为止。
bool DelEle(SqList &L)
{
	int i, count=0;
	if (L.length == 0) return false; //如果线性表为空,返回错误
	for (i = 1; i < L.length; i++)
	{
		if (L.data[i] == L.data[i - 1]) //如果后一个元素与前一个元素值相同
			count++; //计数值+1
		else L.data[i - count] = L.data[i]; //如果不相同,则将后面的元素往前移count位
	}
	L.length -= count; //修改线性表的当前长度
	return true;
}
DelEle(L);

程序分析:

  • !注意: 是有序顺序表,值相同的元素一定在连续的位置上,用类似于直接插入排序的思想。
  • 运行结果:
    在这里插入图片描述
  • 时间复杂度:O(n);空间复杂度:O(1)

如果是单链表:

void DelEle(LinkList& L)
{
	LNode* p = L->next,*q;
	while (p->next != NULL)
	{
		if (p->data==p->next->data )
		{
			q = p->next;
			p->next = q->next;
			free(q);
		}
		else p = p->next;
	}
}

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

  • 算法思想:首先,按顺序不断取下两个顺序表表头较小的结点存入新的顺序表中。然后,哪个表还有剩余,将剩下的部分加到新的顺序表后面。
bool ConEle(SqList L1,SqList L2,SqList &L)
{
	int i=0, j=0, k=0;
	while (i < L1.length && j < L2.length) //i的循环范围为两个线性表中最短的长度
	{
		if (L1.data[i] <= L2.data[j]) //两两比较,将较小的值存入新表
			L.data[k++] = L1.data[i++];
		else L.data[k++] = L2.data[j++];
	}
	while (i < L1.length) //说明第一个表的长度大于第二个表
		L.data[k++] = L1.data[i++]; //将第一个表多余的元素全部复制到新表最后
	while (j < L2.length) //说明第二个表的长度大于第一个表
		L.data[k++] = L2.data[j++]; //将第二个表多余的元素全部复制到新表最后
	L.length = k+1;
	return true;
}
ConEle(L1,L2,L);

程序分析:

  • 运行结果:
    在这里插入图片描述
  • 时间复杂度:O(L1.length+L2.length);空间复杂度:O(L1.length+L2.length)

如果是单链表

void ConEle(LinkList& L1, LinkList& L2)
{
	LNode* p = L1->next, * q = L2->next, * r = L1,*s;
	L1->next = NULL;
	while (p != NULL && q != NULL)
	{
		if (p->data > q->data)
		{
			r->next = q;
			q = q->next;
		}
		else
		{
			r->next = p;
			p = p->next;
		}
		r = r->next;
	}
	while (q != NULL)
	{
		r->next = q;
		q = q->next;
		r = r->next;
	}
	
	while (p != NULL)
	{
		r->next = p;
		p = p->next;
		r = r->next;
	}
	r->next = NULL;
}
  • 第1个whlie循环在最情下的执行次数为O(Listlength(LA)+ Listlength(LB));第2个while循环在最坏情况下的执行次数为
    O(Listlength(LA));第3个while循环在最坏情况下的执行次数为O(Listlength(LB)。所以算法时间复杂度为O(Listlength(LA)+ Listlength(LB))。实际上,每个算法都恰好只扫描LA和LB有序表一次。
  • 说明:两个长度分别为m、n的有序表A和B采用二路归并算法,在最好情下元素的比较次数为MIN(m,n),如A=(1,2,3),B=(5,6,7,8,9),元素比次数为3;在最坏情况下元素的比较次数为m+n-1,如A=(2,4,6),B=(1,3,5,7),元素比次数为6。

8. 已知在一维数组 A [ m + n ] A[m+n] A[m+n]中依次存放两个线性表 ( a 1 , a 2 , a 3 , … , a m ) (a_1,a_2,a_3,…,a_m) (a1,a2,a3,,am) ( b 1 , b 2 , b 3 , . . . , b n ) (b_1,b_2,b_3,...,b_n) (b1,b2,b3,...,bn)。试编写一个函数,将数组中两个顺序表的位置互换,即将 ( b 1 , b 2 , b 3 , . . . , b n ) (b_1,b_2,b_3,...,b_n) (b1,b2,b3,...,bn)放在 ( a 1 , a 2 , a 3 , … , a m ) (a_1,a_2,a_3,…,a_m) (a1,a2,a3,,am)的前面。

  • 算法思想:先将数组A[m+n]中的全部元素 ( a 1 , a 2 , a 3 , . . . , a m , b 1 , b 2 , b 3 , … , b n ) (a_1,a_2,a_3,...,a_m,b_1,b_2,b_3,…,b_n) (a1,a2,a3,...,am,b1,b2,b3,,bn)原地逆置为 ( b n , b n − 1 , b n − 2 , . . . , b 1 , a m , a m − 1 , a m − 2 , . . . , a 1 ) (b_n,b_{n-1}, b_{n-2},...,b_1,a_m,a_{m-1},a_{m-2},...,a_1) (bn,bn1,bn2,...,b1,am,am1,am2,...,a1),再对前n个元素和后m个元素分别使用逆置算法,即可得到 ( b 1 , b 2 , b 3 , … , b n , a 1 , a 2 , a 3 , . . . , a m ) (b_1,b_2,b_3,…,b_n,a_1,a_2,a_3,...,a_m) (b1,b2,b3,,bn,a1,a2,a3,...,am),从而实现顺序表的位置互换
void Reverse(SqList& L,int length1,int length2)
{
	int temp, m = length1, n = length2 - 1;
	int mid = (length2 - length1)/2;
	for (int i = 0; i < mid; i++)
	{
		temp = L.data[m];
		L.data[m] = L.data[n];
		L.data[n] = temp;
		m++; n--;
	}
}
Reverse(L, 0, MaxSize);
Reverse(L,0,L2.length);
Reverse(L, L2.length, MaxSize);

程序分析:

  • 运行结果:
    在这里插入图片描述
  • 三个Reverse函数的时间复杂度分别为: O ( m + n 2 ) 、 O ( n 2 ) 、 O ( m 2 ) O(\frac{m+n}{2})、O(\frac{n}{2})、O(\frac{m}{2}) O(2m+n)O(2n)O(2m)
  • 时间复杂度:O(m+n);空间复杂度:O(1)

9. 线性表 ( a 1 , a 2 , a 3 , … , a n ) (a_1,a_2,a_3,…,a_n) (a1,a2,a3,,an)中的元素递增有序且按顺序存储于计算机内。要求设计一个算法,完成用最少时间在表中查找数值为x的元素,若找到,则将其与后继元素位置相交换,若找不到,则将其插入表中并使表中元素仍递增有序。

  • 顺序存储的线性表递增有序,可以顺序查找,也可以折半查找。这里使用折半查找法。
void ExchEle(SqList& L, int x)
{
	int low = 0, high = L.length - 1, mid, t, i; //low和high指向顺序表下界和上界的下标
	while (low <= high)
	{
		mid = (low + high) / 2; //找到中间位置
		if (L.data[mid] == x) break; //找到x,退出while循环
		else if (L.data[mid] < x) low = mid + 1; //到中点mid的右半部分去找
		else high = mid - 1;//到中点mid的左半部分去找
	}
	if (L.data[mid] == x && mid != L.length - 1) //若最后一个元素与x相等,则不存在与其后继交换的操作
	{
		t = L.data[mid];
		L.data[mid] = L.data[mid + 1];
		L.data[mid + 1] = t;
	}
	
	if (low > high) //查找失败,插入数据元素x
	{ 
		for (i = L.length - 1; i > high; i--) 
		L.data[i + 1] = L.data[i]; //后移元素
		L.data[i + 1] = x; //插入x
	}
}
ExchEle(L,5); 

程序分析:

  • 运行结果:
    在这里插入图片描述

10.设将n(n>1)个整数存放到一维数组R中。将R中保存的序列循环左移p(0<p<n)个位置,即将R中的数据由 ( X 0 , X 1 , … , X n − 1 ) (X_0,X_1,…,X_{n-1}) (X0,X1,,Xn1)变换为 ( X p , X p + 1 , … , X n ー 1 , X 0 , X 1 , … , X p − 1 ) (X_p,X_{p+1},…,X_{nー1},X_0,X_1,…,X_{p-1}) (Xp,Xp+1,,Xn1,X0,X1,,Xp1)

算法思想:参照第8题。

void DivEle(SqList &L, int start, int end)
{
	int i = start, j = end, mid = (j - i) / 2, t;
	//start是数组交换的起始下标值,end是数组交换的最后一个下标值
	//mid是数组交换需要循环的次数
	while(mid)
	{
		t = L.data[i]; L.data[i] = L.data[j-1]; L.data[j-1] = t;
		i++; j--; //数组前面的元素下标值不断增加,后面的元素下标值不断减少
		mid--; 循环次数-1
	}
}

void LefMov(SqList &L, int x)
{
	DivEle(L, 0, L.length); //第一次交换:数组下标范围0~9
	DivEle(L, 0, L.length-x); //第二次交换:数组下标范围0~6
	DivEle(L, L.length - x, L.length); //第二次交换:数组下标范围6~9
}
LefMov(L,3);

程序分析:

在这里插入图片描述

  • 运行结果:
    在这里插入图片描述
  • 三个DivEle函数的时间复杂度分别为: O ( n 2 ) 、 O ( n − x 2 ) 、 O ( x 2 ) O(\frac{n}{2})、O(\frac{n-x}{2})、O(\frac{x}{2}) O(2n)O(2nx)O(2x)
  • 时间复杂度:O(n);空间复杂度:O(1)

11. 一个长度为L(L≥1)的升序序列S,处在第 [ L 2 ] [\frac{L}{2}] [2L]个位置的数称为S的中位数。两个序列的中位数是含它们所有元素的升序序列的中位数。现在有两个等长升序序列A和B,找出两个序列A和B的中位数。

  • 算法思想:方法①-找一个新的存储空间L,将A和B的升序序列放进L,L的中位数即为序列A和B的中位数。
    方法②-类比方法①的思想,但是不找新的空间存储它们,找到第 [ L 2 ] [\frac{L}{2}] [2L]个位置的元素,然后返回该值。

方法一:参考第7题。

int FinMid(SqList L1,SqList L2,SqList &L)
{
	//将A、B这两个有序顺序表合并为一个新的有序顺序表L
	int i=0, j=0, k=0;
	while (i < L1.length && j < L2.length) 
	{
		if (L1.data[i] < L2.data[j])
			L.data[k++] = L1.data[i++];
		else L.data[k++] = L2.data[j++];
	}
	if (i >= L1.length) L.data[k++] = L2.data[j++];
	if (j >= L2.length) L.data[k++] = L1.data[i++];
	return L.data[L.length / 2-1]; //返回L的中位数
}

方法二:

int FinMid(SqList L1,SqList L2,SqList &L)
{
	int i=0, j=0, k=0;
	while (i < L1.length && j < L2.length)
	{
		if (L1.data[i] <= L2.data[j]) i++;
		else j++;
		if (i + j == (L.length - 1) / 2) break; //找到L/2的位置
	}
	if (i == j) //如果循环结束后i=j,就比较i,j位置上的元素值
	{
		if (L1.data[i] < L2.data[j]) return L1.data[i]; //较小的值即为中位数
		return L2.data[j];
	}
	else //如果循环结束后i≠j
	{
		if (i < j) return L1.data[i]; //如果j>i,则返回A中下标为i的数
		return L2.data[j]; //如果i>j,则返回B中下标为j的数
	}
}

程序分析:

方法一:

  • 运行结果:在这里插入图片描述
  • 时间复杂度:O(n);空间复杂度:O(n)

方法二:

  • 运行结果:在这里插入图片描述
  • 时间复杂度:O(n);空间复杂度:O(1)

12. 一个整数序列 A = ( a 0 , a 1 , , … , a n − 1 ) A=(a_0,a_1,,…,a_{n-1}) A=(a0,a1,,,an1),其中 0 ≤ a i ≤ m ( 0 ≤ i < n ) 0≤a_i≤m(0≤i<n) 0aim(0i<n),若存在 a p 1 = a p 2 = . . . = a p m = x a_{p1}=a_{p2}=...=a_{pm}=x ap1=ap2=...=apm=x m > n 2 ( 0 ≤ p k < n , 1 ≤ k ≤ m ) m>\frac{n}{2}(0≤p_k<n,1≤k≤m) m>2n(0pk<n1km),则称x为A的主元素。假设A中的n个元素保存在一个一维数组中,找出A的主元素。若存在主元素,则输出该元素;否则输出-1。

  • 算法思想:创建一个和L等长的数组b,将数组b中所有元素值赋初值0。循环遍历,用数组b来统计元素出现的次数,数组的下标与i的值相对应。再次循环,找出数组b中的最大值并记录该值的数组下标。判断此最大值是否 > n 2 >\frac{n}{2} >2n,如果是,则返回标记的数组下标值,否则返回-1。
int FinEle(SqList L)
{
	int i = 0, max, mark;
	int b[InitSize] = { 0 }; //定义数组b,并赋值
	for (i = 0; i < L.length; i++) //循环遍历
		b[L.data[i]]++ ; //统计L中各元素出现的次数
	max = b[0]; //假设第一个元素为最大值
	for (i = 1; i < L.length; i++) //循环遍历
		if (b[i] > max) //如果该数大于当前标记的最大值
		{
			max = b[i]; //max重新赋值
			mark = i; //记录最大值的下标
		}
	if (b[mark] > L.length / 2) return mark; //判断该数出现的次数是否大于长度的一半
	else return -1;
}
FinEle(L);

程序分析:

在这里插入图片描述

  • 运行结果:在这里插入图片描述
  • 时间复杂度:O(n);空间复杂度:O(n)

13. 给定一个含n(n≥1)个整数的数组,找出数组中未出现的最小正整数。

  • 算法思想:参照第12题。确定完b数组之后,再次循环遍历,找到数组中第一个为0的数组下标,返回该下标的值。
int FindEle(SqList L)
{
	int i = 0;
	int b[InitSize] = { 0 }; //定义数组b,并赋值
	for (i = 0; i < L.length; i++) //循环遍历
		if(L.data[i]>=0) //统计L中每个正整数出现的次数
			b[L.data[i]]++ ; //计数
	for (i = 1; i < L.length; i++) //循环遍历
		if (b[i] == 0) //找到数组中第一个值为0的下标
			return i; //返回该下标
}
FindEle(L);

程序分析:

在这里插入图片描述

  • 运行结果:
    在这里插入图片描述
  • 时间复杂度:O(n);空间复杂度:O(n)

14. 定义三元组( a , b , c a,b,c a,b,c)( a 、 b 、 c a、b、c abc均为正数)的距离 D = ∣ a − b ∣ + ∣ b − c ∣ + ∣ c − a ∣ D=|a-b|+|b-c|+|c-a| D=ab+bc+ca。给定3个非空整数集合 S 1 、 S 2 S_1、S_2 S1S2 S 3 S_3 S3,按升序分别存储在3个数组中。计算并输出所有可能的三元组 ( a , b , c ) ( a ∈ S 1 , b ∈ S 2 , c ∈ S 3 ) (a,b,c)(a∈S_1,b∈S_2,c∈S_3) (a,b,c)(aS1,bS2,cS3)中的最小距离。

int MinDis(SqList a,SqList b,SqList c)
{
	int i, j, k, count = 0, min, mark , arr[60];
	for (i = 0; i < a.length; i++) //a
		for (j = 0; j < b.length; j++) //b
			for (k = 0; k < c.length; k++) //c
			{
				arr[count++] = abs(a.data[i] - b.data[j]) + abs(b.data[j] - c.data[k]) + abs(c.data[k] - a.data[i]); //将计算的距离放入数组arr中
				printf("(%d ,%d ,%d)\n", a.data[i], b.data[j], c.data[k]); //输出三元组的值
			}
	min = arr[0]; //假设数组的第一个元素为最小值
	for (i = 1; i < 60; i++) //循环遍历数组
		if (arr[i] < min) //如果出现比min小的值
		{
			min = arr[i]; //覆盖之前min的值
			mark = i; //记录该值的数组下标
		}
	return arr[mark]; //返回数组中最小的值
}
MinDis(S1, S2, S3);

程序分析:

  • 该算法需要循环嵌套三次,另外需要创建一个数组b存放每次计算的距离值。循环完后需要再次遍历数组b,找出其中的最小值。这种方法在时间和空间上都很大,不是一个很好的算法。
    在这里插入图片描述
  • 运行结果:在这里插入图片描述
  • 时间复杂度: O ( n 3 ) O(n^3) O(n3);空间复杂度: O ( S 1 . l e n g t h × S 2 . l e n g t h × S 3 . l e n g t h ) O(S_1.length×S_2.length×S_3.length) O(S1.length×S2.length×S3.length)
  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值