一.你能结合自己的编程实践,谈一下数据结构和算法之间的关系吗?
**数据结构:**指的是数据元素之间的抽象的相互关系,并不涉及数据元素的具体内容。这种结构关系将数据组织成为某种形式的信息表,以便程序进行加工。而这信息的表示和组织形式又将直接影响加工程序的效率。
**算法:**是对特定问题求解步骤的一种描述,是指令的有限序列。
二.简单排序算法
1、 编程实现(直接) 冒泡排序算法、(直接)选择排序算法和(直接)插入排 序算法,分析其时间和空间复杂度。
**编程实现排序算法思路:**建立一个排序类,成员属性为一个容量为一百的数组(为了统一变量,也可以根据用户输入值,自动创建不同容量大小的随机数组,需要在堆区开辟数组,并重构析构函数手动释放)
class sort
{
public:
//无参构造函数,建立一个容量为100的随机数数组
sort()
{
srand((int)time(0)); // 产生随机种子 把0换成NULL也行
for (int i = 0; i < 100; i++)
{
Arr[i] = rand() % 100;
}
}
//打印函数,输出随机数数组
void sortprint()
{
if (this == NULL) {
return;
}
for (int i = 0; i < 100; i++)
{
cout << Arr[i] << " ";
}
cout << endl;
}
//冒泡排序函数
void BubbleSort();
//选择排序函数
void SelectSort();
//插入排序函数
void InsertSort();
//交换函数
void swap(int &a, int &b)
{
if (this == NULL) {
return;
}
int temp = a;
a = b;
b = temp;
}
public:
int Arr[100];
};
**冒泡排序算法基本思想:**把关键字较小的记录看成是“较轻的”,所以应当往上浮起。从后往前(或者从前往后)两两比较相邻元素的值,若为逆序则交换他们,直到比较完成,称之为第一趟冒泡。下一趟冒泡时,已经确定的最小元素将不再参与比较,每趟冒泡都会将当前序列的最小元素(或者最大元素)放到序列的最终位置,最多n-1躺冒泡,就能使序列元素有序。
时间复杂度:最好情况下,初始序列有序,只需经过第一趟冒泡排序,便结束循环,共比较了n-1次,此时的时间复杂度为O(n)。最坏情况下,初始序列为逆序,共需经过n-1趟排序,第i次排序需要进行n-i次比较,此时的时间复杂度为O(n2)。平均情况下,算法的时间复杂度也为O(n2)。
空间复杂度:算法实现只使用了有限个辅助单元,故空间复杂度为O(1)。
void BubbleSort()
{
if (this == NULL) {
return;
}
int i, j;
for (i = 0; i <= 98; i++)
{
bool flag = false;
for (j = 99; j >= i + 1; j--)
if (Arr[j] < Arr[j - 1])
{
swap(Arr[j], Arr[j - 1]);
flag = true;
}
if (flag == false)
return;
}
}
**选择排序算法基本思想:**每一趟在n-i+1(i=1,2,…n-1)个记录中选取关键字最小的记录作为有序序列中第i个记录。
时间复杂度:在该算法中,元素比较次数与序列初始情况无关,平均时间复杂度为O(n2)。
空间复杂度:该算法仅使用了有限个辅助单元,空间复杂度为O(1)。
void SelectSort()
{
for (int i = 0; i < 99; i++) {
int min = i;
for (int j = i + 1; j < 100; j++)
if (Arr[j] < Arr[min])
min = j;
if (min != i)
swap(Arr[i], Arr[min]);
}
}
**插入排序算法基本思想:**每次将一个待排序的记录按其关键字大小插入前面已经排好的子序列中,直到全部记录插入完成。
**时间复杂度:**最好情况下,初始序列有序,此时每插入一个元素只需要比较一次,时间复杂度为O(n)。最坏情况下,初始序列为逆序,时间复杂度为O(n2)。平均情况下,算法的时间复杂度为O(n2)。
**空间复杂度:**算法仅使用了有限个辅助单元,空间复杂度为O(1)。
void InsertSort()
{
if (this == NULL) {
return;
}
int i, j, r;
for(i=1;i<=99;i++)
if (Arr[i] < Arr[i - 1]) {
r = Arr[i]; //r相当于一个哨兵
for (j = i - 1; r < Arr[j]; --j)
Arr[j + 1] = Arr[j];
Arr[j + 1] = r;
}
}
三种算法实验结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iUVJmTPh-1677331824026)(.\assets\9I3FQ46]58IPZZ9%@`EA3V3.png)]
2、 编程实现(直接) 冒泡排序、(直接)选择排序和(直接)插入排序的递归算法,分析其时间和空间复杂度。
实验选用的数组是容量为20的随机生成数组
void SortBuild(int* Arr)
{
srand((int)time(0)); // 产生随机种子 把0换成NULL也行
for (int i = 0; i < 20; i++)
{
Arr[i] = rand() % 100;
}
}
递归实现冒泡排序算法:
时间复杂度:平均情况下,在第i躺排序时,得进行n-i次比较,时间复杂度为O(n2)。
空间复杂度:使用了有限个辅助单元,空间复杂度为O(1)。
//冒泡排序函数
void BubbleSort(int i, int j,int Arr[])
{
if (i > 18)
return;
else if (j < i + 1)
i++, j = 19;
if (Arr[j] < Arr[j - 1])
swap(Arr[j], Arr[j - 1]);
BubbleSort(i, j - 1, Arr);
}
递归实现选择排序算法:
时间复杂度:在该算法中,元素比较次数与序列初始情况无关,平均时间复杂度为O(n2)。
空间复杂度:每次递归调用,都会创建一个临时变量min用于存储待排序数组的最小值,空间复杂度为O(n)。在代码块中,将min变量放入else语句中,在进行下一次递归前该变量会被释放,应该能将空间复杂度优化至O(1)。
//选择排序函数
void SelectSort(int i, int j, int Arr[])
{
int min = i;
if (i >= 19)
return;
else {
for (int j = i + 1; j < 20; j++)
if (Arr[j] < Arr[min])
min = j;
if (min != i)
swap(Arr[i], Arr[min]);
}
SelectSort(i + 1, j , Arr);
}
递归实现插入排序算法:
时间复杂度:从递归层数最深往最浅看,在倒数第i躺排序时,算法需要比较1到i次,平均时间复杂度为O(n2)
空间复杂度:每次递归都会创建临时变量r,i,空间复杂度为O(n)。
void InsertSort(int len, int Arr[])
{
int r, i;//r用于保存当前的最后一个元素
if (len > 1)
{
InsertSort(len - 1, Arr);
r = Arr[len - 1];
for (i = len - 2; i >= 0 && Arr[i] > r; i--)
Arr[i + 1] = Arr[i];//元素后移
Arr[i + 1] = r;//插入正确的位置
}
else
return;
}
三种算法实验结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cDZKViJl-1677331824027)(./assets/4LI2AS34_[5U9X]{Q5TRRV7.png)]
3、 你能用实验的方法验证上述算法时间复杂度的分析结论吗?
三.汉诺塔问题
1、 编程实现汉诺塔问题的递归算法,并分析的时间和空间复杂度。
有A、B、C三根柱子,现在A柱子上面从下往上按照大小顺序放着n片圆盘,规定任何时候小圆盘上面都不能放着大圆盘,且三根柱子之间一次只能移动一个圆盘。请问把A柱子上的圆盘移动到C柱子上面,需要移动多少次?
- 先通过递归将A的前n-1个盘子移动到B
- 再把A第n个盘子移动到C
- 最后将B上的n-1个盘子移动到C
时间复杂度:观察上面的步骤可以发现,T(n)=2T(n-1)+1,算法的时间复杂度为O(2n)。
空间复杂度:算法无需太多辅助单元,空间复杂度为O(1)。
void HannoiTower(int n, char a , char b , char c )
//第二个参数为起始柱,第三个参数为中间柱,第四个参数为目标柱
{
if (n == 0)
{
return ;
}
HannoiTower(n - 1, a, c, b);//先通过递归将A的前n-1个盘子移动到B
i++;//计数
cout << a << "移动至" << c << endl; //再把A第n个盘子移动到C
HannoiTower(n - 1, b, a, c);//最后将B上的n-1个盘子移动到C
}
实验截图:
2、 你能将汉诺塔的递归算法改写成非递归算法吗?并分析其时间和空间复 杂度。
所有的汉诺塔问题,都可以通过重复的两步来解决,同样假设现在A柱上面有n个圆盘,三根柱子的顺序为A-B-C。
- 将三根柱子上最小的圆盘移动到下一根柱子上面
- 除了最小盘移动到的柱子外,判断剩下的两根柱子上面的盘子大小,将较小的圆盘移动到较大的圆盘上面。如果有空杆则移动到空杆上面。
如此重复上述两个步骤,当n为奇数时,最终所有圆盘会放到B柱上;当n为偶数时,最终所有圆盘会放到C柱上。
思路:给A设立一个数组,初始化为1到n,代表盘子从小到大。再给B和C初始化一个相同大小的数组,全为0,代表为空。
void HannoiTower(int n, char a, char b, char c)
//第二个参数为起始柱,第三个参数为中间柱,第四个参数为目标柱
{
if (n == 0)
{
return;
}
int m;
int* a_p=new int[n];//从上至下记录a柱子的盘子大小,初始为1到n
for (int i = 0; i < n; i++)
{
a_p[i] = i + 1;//1为最小盘,0为空
}
int* b_p = new int[n];//从上至下记录a柱子的盘子大小,初始全为0
for (int i = 0; i < n; i++)
b_p[i] = 0;
int* c_p = new int[n];//从上至下记录a柱子的盘子大小,初始全为0
for (int i = 0; i < n; i++)
c_p[i] = 0;
int x , y , z;//用于记录a\b\c盘子移到第几层
x = FindTOP(a_p, n);//找到a柱的顶端
y = FindTOP(b_p, n);//找到b柱的顶端
z = FindTOP(c_p, n);//找到c柱的顶端
while (b_p[y] != 0 || a_p[x] != 0)//a,b柱子还没搬空
{
x = FindTOP(a_p, n);//找到a柱的顶端
y = FindTOP(b_p, n);//找到b柱的顶端
z = FindTOP(c_p, n);//找到c柱的顶端
int index=FindMin(a_p[x],b_p[y],c_p[z]); //比较柱顶圆盘大小
switch (index)
{
case 1:
cout << a << "移动至" << b << endl; //把最小盘移动到下一个柱子上面
b_p[x]= a_p[x];//更新b柱子圆盘
y = x;
a_p[x] = 0;//将a柱子顶端盘置空
x = FindTOP(a_p, n);//找到a柱的顶端
z = FindTOP(c_p, n);//找到c柱的顶端
if (0<a_p[x]< c_p[z])//两个柱子不为空
{
cout << a << "移动至" << c << endl;
c_p[x] = a_p[x];
z = x;
a_p[x] = 0;
}
else if(a_p[x] > c_p[z]>0)//两个柱子不为空
{
cout << c << "移动至" << a << endl;
a_p[z] = c_p[z];
x = z;
c_p[z]=0;
}
else if (a_p[x] == 0&& c_p[z]!=0)
{
cout << c << "移动至" << a << endl;
a_p[z] = c_p[z];
x = z;
c_p[z] = 0;
}
else if(c_p[x] == 0 && a_p[x] != 0)
{
cout << a << "移动至" << c << endl;
c_p[x] = a_p[x];
z = x;
a_p[x] = 0;
}
case 2:
cout << b << "移动至" << c << endl; //把最小盘移动到下一个柱子上面
c_p[y] = b_p[y];//更新c柱子圆盘
z = y;
b_p[y] = 0;//将b柱子顶端盘置空
x = FindTOP(a_p, n);//找到a柱的顶端
y = FindTOP(b_p, n);//找到c柱的顶端
if (0 < a_p[x] < b_p[y])//两个柱子不为空
{
cout << a << "移动至" << b << endl;
b_p[x] = a_p[x];
y = x;
a_p[x] = 0;
}
else if (a_p[x] > b_p[z] > 0)//两个柱子不为空
{
cout << b << "移动至" << a << endl;
a_p[y] = b_p[y];
x = y;
b_p[y] = 0;
}
else if (a_p[x] == 0 && b_p[y] != 0)
{
cout << b << "移动至" << a << endl;
a_p[y] = b_p[y];
x = y;
b_p[y] = 0;
}
else if (b_p[x] == 0 && a_p[x] != 0)
{
cout << a << "移动至" << b << endl;
b_p[x] = a_p[x];
y = x;
a_p[x] = 0;
}
case 3:
cout << c << "移动至" << a << endl; //把最小盘移动到下一个柱子上面
a_p[z] = c_p[z];//更新a柱子圆盘
x = z;
c_p[z] = 0;//将c柱子顶端盘置空
y = FindTOP(b_p, n);//找到a柱的顶端
z = FindTOP(c_p, n);//找到c柱的顶端
if (0 < b_p[x] < c_p[y])//两个柱子不为空
{
cout << b << "移动至" << c << endl;
c_p[y] = b_p[y];
z = y;
b_p[y] = 0;
}
else if (b_p[y] > c_p[z] > 0)//两个柱子不为空
{
cout << c << "移动至" << b << endl;
b_p[z] = c_p[z];
y = z;
c_p[z] = 0;
}
else if (b_p[y] == 0 && c_p[z] != 0)
{
cout << c << "移动至" << b << endl;
b_p[z] = c_p[z];
y = z;
c_p[z] = 0;
}
else if (c_p[z] == 0 && b_p[y] != 0)
{
cout << b << "移动至" << c << endl;
c_p[y] = b_p[y];
z = y;
b_p[y] = 0;
}
}
x = FindTOP(a_p, n);//找到a柱的顶端
y = FindTOP(b_p, n);//找到b柱的顶端
z = FindTOP(c_p, n);//找到c柱的顶端
}
delete[] a_p;
delete[] b_p;
delete[] c_p;
}
int FindMin(int a, int b, int c)//用于寻找顶端盘子最小的柱子
{
if (a > 0) {//a不空
if (b > c > 0) {//b\c不空
if (a < c)
return 1;
else
return 3;
}
else if (0 < b < c) {//b\c不空
if (a < b)
return 1;
else
return 2;
}
else if (b == 0 && c != 0) {
if (a < c)
return 1;
else
return 3;
}
else if (c == 0 && b != 0) {
if (a < b)
return 1;
else
return 2;
}
else if (b == 0 && c == 0)//bc空
return 1;
}
else
{//a空
if (b > c > 0) {//b\c不空
return 3;
}
else if (0 < b < c) {//b\c不空
return 2;
}
else if (b == 0 && c != 0) {//ab空
return 3;
}
else if (c == 0 && b != 0) {//ac空
return 2;
}
}
}
int FindTOP(int* arr,int n)//寻找顶端圆盘大小
{
int i;
int TOP = 0;//保存柱顶圆盘的下标
for (i = 0; i < n; i++)
{
if (arr[i] > 0) {
TOP = i;
break;
}
}
return TOP;//返回柱顶圆盘所在数组的下标
}
实验截图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Y1fsnThi-1677331824027)(assets/5564.png)]
3、 你能通过实验来比较汉诺塔问题的递归和非递归算的时间效率吗?
四.角谷猜想
对于任何正整数 n,若是偶数,则除以 2;若是奇数,则乘以 3 加上 1。这 样得到一个新的整数,如果继续进行上述处理,则最后得到的数一定是 1 吗?
1、 编写程序对 100 以内的整数进行测试验证,并找出其中最长的序列。
主函数
void main()
{
int n;
int arr[100];
for (n = 1; n <= 100; n++) {
arr[n - 1] = JiaoGu(n);
}
int i = FindMax(arr);
cout << "序列最长的整数为" << i + 1 << endl;
cout << "最长的序列长度为" << arr[i] << endl;
}
角谷猜想函数
int JiaoGu(int n)
{
int i = 1;//用于记录序列长度
while (n != 1)
{
if (n % 2 == 0) {
n = n / 2;//偶数除以2
}
else {
n = n * 3 + 1;//奇数乘以3加1
}
i++;
}
return i;
}
寻找数组最大值函数
int FindMax(int a[])
{
int i,temp;
temp = a[0];
int max=0;//保存最大值下标
for (i = 1; i < 100; i++)
{
if (a[i] > temp)
{
temp = a[i];
max = i;
}
}
return max;
}
实验结果
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VvBfGXDh-1677331824028)(assets/123.png)]
2、 你能给角谷问题一个肯定或否定的回答吗?
五.交通管制问题
1、 你能编程实现交通管制问题的贪心算法,对任意交叉路口的交通管制?
2、 你能证明该贪心算法的正确性吗?
3、 能根据实际的交通流量等信息,优化该贪心算法吗?
角谷猜想函数
int JiaoGu(int n)
{
int i = 1;//用于记录序列长度
while (n != 1)
{
if (n % 2 == 0) {
n = n / 2;//偶数除以2
}
else {
n = n * 3 + 1;//奇数乘以3加1
}
i++;
}
return i;
}
寻找数组最大值函数
int FindMax(int a[])
{
int i,temp;
temp = a[0];
int max=0;//保存最大值下标
for (i = 1; i < 100; i++)
{
if (a[i] > temp)
{
temp = a[i];
max = i;
}
}
return max;
}
实验结果
[外链图片转存中…(img-VvBfGXDh-1677331824028)]
2、 你能给角谷问题一个肯定或否定的回答吗?
五.交通管制问题
1、 你能编程实现交通管制问题的贪心算法,对任意交叉路口的交通管制?
2、 你能证明该贪心算法的正确性吗?
3、 能根据实际的交通流量等信息,优化该贪心算法吗?