考研数据结构——顺序表相关大题(含解析和代码)

前言

        本文习题均来自25王道考研的教材课后习题,在给出答案的基础上加上自己的分析,方便大家理解学习,部分题目我列出了我本人的解法和王道书的解法,大家对比着看会更有体会。算法大题不需要找到最优算法,考试中直接使用暴力解法就可以,不要在乎时空复杂度,哪怕用最烂的算法,只要写对,并且对应的时空复杂度写对,最多扣3分,如果是次优算法,只扣1-2分。最优算法是为了学习不是为了答题!

        附一些初始化顺序表的基本函数代码,以下代码声明一个顺序表,表中数据是:100到109。

#include<iostream>
using namespace std;
#define InitSize 10
//创建一个顺序表
struct SqList {
	int* data;
	int length;
	int MaxSize = InitSize;
};
typedef SqList SqList;

//初始化表
bool InitSqList(SqList& L) {	
	L.data = (int*)malloc(sizeof(int) * InitSize);		//申请内存空间
	if (L.data == NULL)			//内存分配失败
		return false;
	L.length = 0;
}
//向表中插入一些基本元素,用于初始化表
void InsertSqList(SqList& L) {
	for (int i = 0; i < L.MaxSize; i++) {
		L.data[i] = i + 100;
		L.length++;
	}
}
//输出表L的值。
void PrintSqList(SqList L) {
	for (int i = 0; i < L.length; i++)
		cout << L.data[i] << " ";
	cout << endl;
}
//初始化表,插入一些基本数据并打印
void test01(SqList& L) {
	InitSqList(L);
	InsertSqList(L);
	//PrintSqList(L);
}
int main() {
	SqList L;
    cout<<"输出表L内容如下:"
	test01(L);
	cout << "初始化完成";
	return 0;
}

题目

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

        思路:搜索整个顺序表,查找最小值元素并记住其位置,搜索结束后用最后一个元素填补空出的原来最小值元素的位置,然后让表长 -1 。

bool Delete_Min(SqList& L, int& value) {		//对表L删除操作,并用value保存删除的元素并带出函数外
	if (L.length == 0) {
		cout << "表为空,不合法!" << endl;
		return false;
	}
	value = L.data[0];							//给value一个初始值
	int p = 0;
	for (int i = 0; i < L.length; i++) {		//遍历L的所有元素
		if (L.data[i] < value) {
			value = L.data[i];					//及时更新value的值为当前扫描到的最小值
			p = i;								//p是最小元素的下标
		}
	}
	L.data[p] = L.data[L.length - 1];			//用最后一个元素的值覆盖下标为p的元素的值
	L.length--;									//数组长度减一,表示删除末尾元素
	return true;
}

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

        思路:扫描顺序表 L 的前半部分元素,对于元素 L.data[i](0<-i<L.length/2),将其与后半部分的对应元素L.data[L.length-i-1] 进行交换。

void Reverse(SqList& L) {
	ElemType 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[i] = L.data[L.length - i - 1];
		L.data[L.length - i - 1] = temp;
	}
}

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

        思路:用k记录表L中元素等于x的个数,一边扫描L,一边将不等于x的元素往前移动k位,最后将表长 -k。

void Delete_X(SqList& L, int x) {
	int k = 0;
	for (int i = 0; i < L.length; i++) {
		if (L.data[i] == x)
			k++;
		else if (L.data[i] != x)
			L.data[i - k] = L.data[i];
	}
	L.length -= k;
}

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

        思路:从前向后扫描顺序表L,用k记录值在s和t之间的元素个数,对于扫描的元素,若其不在 [s,t] 区间中,则前移k个位置,否则k++。由于每个不符合条件的元素只移动一次,故算法效率高。

bool Delete_s_t(SqList& L, int s, int t) {
	int k = 0;
	if (s >= t || L.length == 0)		//错误输入!
		return false;
	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];
	}
	L.length -= k;
	return true;
}

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

        因为是有序表,所有值相同的元素一定在连续的位置上,用类似于直接插入排序的思想,初始将第一个元素视为非重复的有序表。之后依次判断后面的元素是否与前面非重复的有序表的最后一个元素相同,若相同,则继续向后判断,若不同,则插入前面的非重复有序表的末尾,直至判断到表尾为止。

bool Delete_Same(SqList& L) {
	if (L.length == 0)
		return false;
	int i, j;								//i存储第一个不相同的元素,j为工作指针
	for (i = 0, j = 1; j < L.length; j++) {
		if (L.data[i] != L.data[j])			//查找下一个与上个元素值不同的元素
			L.data[++i] = L.data[j];		//找到后,将元素前移
	}
	L.length = i + 1;
	return true;
}

//测试数据
void InsertSqList(SqList& L) {
	//插入其他值 10 11 12 12 12 13 14 40 40 50
	L.length = 10;
	L.data[0] = 10;
	L.data[1] = 11;
	L.data[2] = 12;
	L.data[3] = 12;
	L.data[4] = 12;
	L.data[5] = 13;
	L.data[6] = 14;
	L.data[7] = 40;
	L.data[8] = 40;
	L.data[9] = 50;
}

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

        思路:把AB两个表的元素依次比较,小的存入C表,最后会剩一个表,把剩余部分加到C表后面。

bool Merge(SqList A, SqList B, SqList &C) {
	if (A.length + B.length > C.length) {
		cout << "AB表长大于C的表长" << endl;
		return false;
	}
	int i = 0, j = 0, k = 0;
	while (i < A.length && j < B.length) {	//小的存入C表
		if (A.data[i] <= B.data[j])
			C.data[k++] = A.data[i++];
		else {
			C.data[k++] = B.data[j++];
		}
	}
	while (i < A.length)			//还剩一个没有比较完的顺序表
		C.data[k++] = A.data[i++];
	while (j < B.length)
		C.data[k++] = B.data[j++];
	C.length = k;
	return true;
}

7.在一维数组A[m+n]中依次存放两个线性表A和B,分别有m、n个元素。编写一个函数,将数组中两个顺序表的位置互换,即将B放在A的前面。

        思路:先将数组A[m+n]中的全部元素(a1,a2,a3,...,am,b1,b2,b3,...,bn)原地逆置为(bn,...,b2,b1,am,...,a2,a1),然后对前n个元素和后m个元素分别使用逆置算法,即可得到(b1,b2,b3,...,bn,a1,a2,a3,...,am)。这个和第二题的逆置算法稍有不同,不过本质是相同的。

void Reverse(int A[], int left, int right, int arraySize) {
	if (left >= right || right >= arraySize)
		return;
	int mid = (left + right) / 2;
	for (int i = 0; i < mid - left; i++) {		//mid - left是要进行逆置的表长的一半
		int temp = A[left + i];
		A[left + i] = A[right - i];
		A[right - i] = temp;
	}
}
void Exchange(int A[], int m, int n, int arraySize) {
	Reverse(A, 0, m + n - 1, arraySize);
	Reverse(A, 0, n - 1, arraySize);
	Reverse(A, n, m + n - 1, arraySize);
}

8.线性表L(有n个元素)中的元素递增有序且按顺序存储于计算机内。要求设计一个算法,完成用最少时间在表中查找数值为x的元素,若找到,则将其与后继元素位置相交换,若找不到,则将其插入表中并使表中元素依然递增有序。

        思路:顺序存储的线性表递增有序,可以顺序查找,也可以折半查找,根据题目要求需要用折半查找。

void SearchExchangeInsert(SqList& L, int x) {
    //折半查找,目的是为了找到x的下标位置,用mid表示
	int left = 0, right = L.length - 1;
	int mid;
	while (left <= right) {
		mid = (left + right) / 2;
		if (L.data[mid] == x)
			break;
		else if (L.data[mid] < x)
			left = mid + 1;
		else
			right = mid - 1;
	}
	if (L.data[mid] == x && mid != L.length - 1) {		//如果最后一个元素和x相等,则不存在与其后继的交换操作。
		int temp = L.data[mid];
		L.data[mid] = L.data[mid + 1];
		L.data[mid + 1] = temp;
	}
	if (left > right) {			//如果没找到x
		int i;
		for (i = L.length - 1; i > right; i--)
			L.data[i + 1] = L.data[i];
		L.data[i + 1] = x;		//插入x
	}
}

9.给定三个序列A、B、C,长度均为n,且均为无重复元素的递增序列,请设计一个时间上尽可能高效的算法,朱行输出同时存在于这三个序列中的所有元素。

        思路:用三个工作指针从小到大遍历三个数组,当三个下标变量指向的元素相等时,输出并向前推进指针,否则仅移动小于最大元素的下标变量,直到某个下标变量移出数组范围,即可停止。

        每个指针移动次数不超过n次,且每次循环至少有一个指针后移,所以时间复杂度为O(n),算法只用到了常数个变量,空间复杂度为O(1)。

//自己写的:
void SameKey(SqList A, SqList B, SqList C) {
	int i = 0, j = 0, k = 0;		//声明三个指针分别指向三个数组
	while (i < A.length && j < B.length && k < C.length) {	//当三个数组都没有遍历完的时候
		if (A.data[i] == B.data[j] && A.data[i] == C.data[k]) {	//如果满足条件,则打印,并且指针向后移一位
			cout << A.data[i] << " ";
			i++,j++,k++;
		}
		else {
			if (A.data[i] <= B.data[j] && A.data[i] <= C.data[k])	//如果A的元素最小,i++
				i++;
			else if (B.data[j] <= A.data[i] && B.data[j] <= C.data[k])//如果B的元素最小,j++
				j++;
			else if (C.data[k] <= A.data[i] && C.data[k] <= B.data[j])//如果C的元素最小,k++
				k++;
		}
	}
}

//王道书给的,是对数组操作的,本质一样,用了max函数,更简洁了
void SameKey(int A[], int B[], int C[], int n) {
	int i = 0, j = 0, k = 0;
	while (i < n && j < n && k < n) {
		if (A[i] = B[j] && B[j] == C[k]) {
			printf("%d\n", A[i]);
			i++, j++, k++;
		}
		else {
			int maxNum = max(A[i], max(B[j], C[k]));
			if (A[i] < maxNum)
				i++;
			if (B[j] < maxNum)
				j++;
			if (C[k] < maxNum)
				k++;
		}
	}
}

//一些测试数据,调用的是我自己写的那个函数,是对顺序表操作的
bool InitSqList(SqList& L) {	
	L.data = (int*)malloc(sizeof(int) * InitSize);		//申请内存空间
	if (L.data == NULL)			//内存分配失败
		return false;
	L.length = 0;
}
int main() {
	SqList A,B,C;
	InitSqList(A);
	InitSqList(B);
	InitSqList(C);
	A.length = 3;
	B.length = 3;
	C.length = 3;
	A.data[0] = 1;
	A.data[1] = 4;
	A.data[2] = 5;
	B.data[0] = 2;
	B.data[1] = 4;
	B.data[2] = 4;
	C.data[0] = -1;
	C.data[1] = 3;
	C.data[2] = 4;
    SameKey_me(A, B, C);	
	return 0;
}

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

        思路:将问题视为把数组ab转换成数组ba(a代表数组的前p个元素,b代表数组的后n-p个元素),先将a逆置得到“a逆b”,再将b逆置得到“a逆b逆”,最后将整个“a逆b逆”逆置得到ba。用Reverse函数执行逆置操作,可以写出如下代码。

        算法中三个Reverse函数的时间复杂度分别为 O(p/2)、O((n-p)/2)和O(n/2),故所设计的算法的时间复杂度为O(n),空间复杂度为O(1)。

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 Converse(int R[], int n, int p) {
	Reverse(R, 0, p - 1);
	Reverse(R, p, n - 1);
	Reverse(R, 0, n - 1);
}

11.一个长度为L的升序序列S,处在中间的元素是S的中位数,若S为偶数,则处在中间的后一位元素为S的中位数。例如,若S1=(11,13,15,17,19),则S1的中位数是15;两个序列的中位数是他们所有元素的升序序列的中位数,例如,若S2=(2,4,6,8,20),则S1和S2的中位数是11.现在有两个等长升序序列A和B,试设计一个在时间和空间两方面都尽可能高效的算法,找出两个序列A和B的中位数。

思路:分别求两个升序序列A、B的中位数,设为a和b,求序列A、B的中位数过程如下:

  1. 若a=b,则a或b即为所求的中位数,算法结束;

  2. 若a<b,则舍弃序列A中较小的一半,同时舍弃序列B中较大的一半,要求两次舍弃的长度相等;

  3. 若a>b,则舍弃序列A中较大的一半,同时舍弃序列B中较小的一半,要求两次舍弃的长度相等。

        在保留的两个升序序列中,重复过程123,直到两个序列中均只含一个元素时为止,较小者即为所求的中位数。

int M_Search(int A[], int B[], int n) {
	int s1, d1, m1, s2, d2, m2;
	s1 = 0, d1 = n - 1;
	s2 = 1, d2 = n - 1;
	while (s1 != d1 || s2 != d2) {
		m1 = (s1 + d1) / 2;
		m2 = (s2 + d2) / 2;
		if (A[m1] == B[m2])				//满足条件 1
			return A[m1];				
		if (A[m1] < B[m2]) {			//满足条件 2
			if ((s1 + d1) % 2 == 0) {	//若元素个数为奇数
				s1 = m1;				//舍弃A中间点以前的部分,且保留中间点
				d2 = m2;				//舍弃B中间点以后的部分,且保留中间点
			}
			else {						//若元素个数为偶数
				s1 = m1 + 1;			//舍弃A的前半部分
				d2 = m2;				//舍弃B的后半部分
			}
		}
		else {							//满足条件 3
			if ((s1 + d1) % 2 == 0) {	//若元素个数为奇数
				d1 = m1;				//舍弃A中间点以后的部分,且保留中间点
				s2 = m2;				//舍弃B中间点以前的部分,且保留中间点
			}
			else {						//元素个数为偶数
				d1 = m1;				//舍弃A的后半部分
				s2 = m2 + 1;			//舍弃B的前半部分
			}
		}
	}
	return A[s1] < B[s2] ? A[s1] : B[s2];
}

        以上是答案中的算法,是最优算法,时间复杂度为O(log2n),空间复杂度为O(1)。很棒,但不适用于考试,因为你想不到,也写不全。于是有了次优算法:归并排序的思想。

        我们只需要找到两个数组合并起来之后的第“N/2”的下标位置的元素,这就是我们要找的合并数组的中位数。所以用两个指针 i,j,分别指向A,B两个数组,依次比较对应元素大小,把小的那个的指针往后移,当比较了 n 次之后, i 和 j 指向的那个较小的元素就是要找的中位数。

void M_Search(SqList A, SqList B) {
	int n = A.length;					//n为单个表长
    int i = 0, j = 0;			
	while(i + j != n - 1){				//注意这里,数组下标从0开始,表长应该减一,否则会溢出
		if (A.data[i] <= B.data[j])
			i++;						//小指针往后移
		else if (A.data[i] > B.data[j])
			j++;						//小指针往后移
	}
	if (A.data[i] <= B.data[j])
		cout << "A和B的中位数为:" << A.data[i];
	else if (A.data[i] > B.data[j])
		cout << "A和B的中位数为:" << B.data[j];
}

代码解析

  • 两个表共有 2n 个元素,需要比较 n 个元素,第 n 个元素即为我们要找的中位数;

  • 每次比较之后指针指向的较小的那个元素的指针往后移;

  • 判定循环结束条件为 i+j!=n-1 ,注意是 n-1 不是 n ,因为表长为 n 的话数组下标最大是 n-1 ,体会一下;

  • 最后输出结果是两个指针中指向的较小的那个元素。

  • 14
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
西南交通大学的考研数据结构和C语言真题主要涵盖了数据结构和C语言的基本概念、常见算法和数据结构的应用,是考研复习中的重点和难点。 数据结构部分的真题主要涉及线性表、栈和队列、链表和树、图和排序等知识点。例如,可能会出现关于数组的插入、删除和查找操作以及对其时间复杂度的分析题目,还可能会要求设计和实现单链表、二叉树或图等数据结构,并进行相应的操作和应用。对于这些题目,考生需要熟悉各种数据结构的特点、使用方法和算法,能够分析算法的时间复杂度和空间复杂度,并灵活应用到实际问题中。 C语言部分的真题主要考察C语言的基本语法、指针和内存管理、函数和库等方面的知识。可能会出现关于函数的声明和定义、指针的使用、内存动态分配和释放等方面的题目。考生需要对C语言的语法、特性和常用库函数有一定的掌握,能够理解和分析C语言程序的执行过程和内存管理机制。 对于准备西南交通大学考研的考生来说,要复习数据结构和C语言,首先要掌握基础概念和常用算法和数据结构的原理和应用。其次,要多做真题和模拟题,加深对知识的理解和应用。同时,还要关注最新的考研动态和备考资料,及时调整和完善复习计划。通过系统的学习和不断的练习,相信考生一定能够顺利应对西南交通大学考研数据结构和C语言的考试。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值