简单选择排序(蛮力法 )
void SelectSort(int a[], int n)
{
int i, j, t;
for(i = 0; i < n - 1; i++)
{
int maxindex = i;
for(j = 1 + i; j < n; j++)
if(a[maxindex] < a[j])
maxindex = j;
if(maxindex != i)
{
t = a[maxindex];
a[maxindex] = a[i];
a[i] = t;
}
}
}
简单选择排序(递归)
-
递归函数:
f(a, n, i)用于在无序区 a[i … n-1] (共n-i个元素)中选择最小元素放到a[i]处,是 “大问题” ,f(a, n, i+1)用于在无序区 a[i+1 … n-1] (共n-i-1个元素)中选择最小元素放到a[i+1]处,是 “小问题”。 -
递归模型:
f(a, n ,i)
{
if (i == n-1)
算法结束;
else
{
通过简单比较挑选a[i ... n-1]中的最小元素;
最小元素a[k]放到a[i]处;
f(a, n, i+1);
}
}
- 算法实现:
#include<cstdio>
#include<iostream>
using namespace std;
void swap(int &x, int &y) //参数为引用类型
{
int temp = x;
x = y;
y = temp;
}
void SelectSort(int a[], int n, int i)
{
if (i == n-1)
return;
else
{
//1、通过简单比较挑选a[i ... n-1]中的最小元素;
int k = i; //初始化k为[i ... n-1]中最小元素所在索引
for (int j = i+1; j < n; j++)
if (a[j] < a[k])
k = j;
//2、最小元素a[k]放到a[i]处;
if (k != i)
swap(a[i], a[k]);
//3、f(a, n, i+1);
SelectSort(a, n, i+1);
}
}
void display(int a[], int n)
{
for (int i = 0; i < n; i++)
cout << a[i] << " ";
cout << endl;
}
int main()
{
int n = 10;
int a[] = {2,5,1,7,10,6,9,4,3,8};
cout << "排序前:";
display(a, n);
SelectSort(a, n, 0);
cout << "排序后:";
display(a, n);
return 0;
}
冒泡排序(蛮力法)
void BubbleSort(int a[], int n)
{
int i, j, t;
for(i = 0; i < n - 1; i++)
for(j = 1 + i; j < n; j++)
if(a[i] > a[j])
{
t = a[i];
a[i] = a[j];
a[j] = t;
}
}
冒泡排序(递归)
-
递归函数:
f(a, n, i)用于在无序区 a[i … n-1] (共n-i个元素)中找出小元素放到a[i]处,是 “大问题” ,f(a, n, i+1)用于在无序区 a[i+1 … n-1] (共n-i-1个元素)中找出小元素放到a[i+1]处,是 “小问题”。 -
递归模型:
f(a, n ,i)
{
if (i == n-1)
算法结束;
else
{
对a[i ... n-1]中的元素序列从a[n-1]开始进行相邻元素的比较;
若相邻两元素反序则将两者交换(设有交换发生标志,若发生过两元素的交换,则该标志的值为true);//设置“交换发生标志”的原因:如果没有发生交换,说明后面的元素都是有序的了,所以程序可以直接结束了(一层层返回回去——退栈)
若交换标志为false,则return; 否则,f(a, n, i+1);
}
}
- 算法实现:
#include<cstdio>
#include<iostream>
using namespace std;
void swap(int &x, int &y) //参数为引用类型
{
int temp = x;
x = y;
y = temp;
}
void BubbleSort(int a[], int n, int i)
{
if (i == n-1)
return;
else
{
//1、通过交换方式将无序区中的最小元素放置在a[i]处
bool exchange = false;
for (int j = n-1; j > i; j--) //从后往前两两进行比较,最终把最小的排放在a[i]位置上
if (a[j] < a[j-1])
{
swap(a[j], a[j-1]);
exchange = true;
}
//2、判断交换标志的值,从而确定是否继续递归
if (!exchange)
return;
else
BubbleSort(a, n, i+1);
}
}
void display(int a[], int n)
{
for (int i = 0; i < n; i++)
cout << a[i] << " ";
cout << endl;
}
int main()
{
int n = 10;
int a[] = {2,5,1,7,10,6,9,4,3,8};
cout << "排序前:";
display(a, n);
BubbleSort(a, n, 0);
cout << "排序后:";
display(a, n);
return 0;
}
递归的简单选择排序与递归的冒泡排序的区别仅在与递归算法中“把无序区的最小元素放置a[i]处”选用的方法不同,前者使用简单比较的方式,后者使用交换方式。
快速排序(分治)
- 二分法思想:每次归位一个元素,将整个无序序列一分为二,对这两个子序列分别再次采用同样的方式进行归并、一分为二(递归),直至子序列的长度为1或0为止。
- QuickSort()为递归函数,递归地“将序列一分为二,并进行归并(实现“比基准大的都在基准的右边,比基准小的都在基准的左边”这一目标 P86图3.2)”,不过它又被封装成了Partition()函数。
- 其实主要就是实现“将序列一分为二,并进行归并-排序”,也就是Partition()函数。我对它的流程有点疑惑,不是很能理解,但自己举例按照程序的算法走了一遍,发现是这个样子的!很流畅!就是把比基准大的移到后面,把基准小的移到前面,最后把基准元素放到正确的位置,然后返回这个位置(方便再次对基准两侧的无序序列进行排序)。
#include<cstdio>
#include<iostream>
using namespace std;
int Partition(int a[], int s, int t)
{
int i=s, j = t, tmp = a[s];//无序序列中的第一个元素作为基准tmp
//从序列两端交替向中间扫描,直到i=j为止
while (i != j)
{
//从右向左扫描,扫描到比基准小的一个数时,将a[j]前移到a[i]的位置
while (j > i && a[j] >= tmp)
j--;
a[i] = a[j];
//从左向右扫描,扫描到比基准大的一个数时,将a[i]后移到a[j]的位置
while (j > i && a[i] <= tmp)
i++;
a[j] = a[i];
}
a[i] = tmp;
return i;
}
void QuickSort(int a[], int s, int t) //对a[s..t]元素序列进行递增排序
{
if (s < t) //序列内至少存在两个元素
{
int i = Partition(a, s, t); //划分并归并,返回归并后的基准元素的位置
QuickSort(a, s, i-1); //继续对归并后的基准元素两侧的子序列进行排序
QuickSort(a, i+1, t); //继续对归并后的基准元素两侧的子序列进行排序
}
}
void display(int a[], int n)
{
for (int i = 0; i < n; i++)
cout << a[i] << " ";
cout << endl;
}
int main()
{
int n = 10;
int a[] = {2,5,1,7,10,6,9,4,3,8};
cout << "排序前:";
display(a, n);
QuickSort(a, 0, n-1);
cout << "排序后:";
display(a, n);
return 0;
}
归并排序(分治)
自底向上的二路归并排序
- 从每个无序序列长度为1开始,两两相邻无序序列进行归并(P89图3.4),这即自底向上的二路归并排序,它由MergeSort()函数实现。但是MergeSort()函数并不是直接把这个功能实现出来,而是又将这一功能进行更小子功能的封装,由其他的函数再一步步实现。
- 首先,MergeSort()函数把上面那一功能的实现扔给了MergePass()函数来实现一趟的所有无序序列的两两归并,MergePass()函数又扔给Merge()函数实现一趟中的一次的两两归并。也就是说,MergeSort()函数需要实现多趟两两归并(每一趟无序序列的长度分别为length, 2length, 4length…),所以就让MergePass()函数来帮助自己实现一趟的两两归并;MergeSort()函数虽然只需要实现一趟的两两归并,但一趟中的两两归并又要执行很多次,于是MergeSort()函数让Merge()函数来帮助自己实现一次的两两归并操作。。。
- 然后还需要理解的是,“MergeSort()函数需要几次MergePass()函数的帮助?” “MergePass()函数需要几次Merge()函数的帮助?”。也就是确定for循环的次数,也就是思考for循环怎么写。
- 对于第一个问题,答案很容易知道,前面也已经说了“每一趟无序序列的长度分别为length, 2length, 4length…”,所以for循环这样写:
for (length = 1; length < n; length = 2 * length)
MergePass(a, length, n);
- 对于后面这个问题,看笔记上的,已解释清楚。
#include<cstdio>
#include<stdlib.h>
#include<iostream>
using namespace std;
void Merge(int a[], int low, int mid, int high)
/*对a[low, mid]、a[mid+1, high]这两个相邻序的有序列进行归并*/
{
int *tmpa; //用于保存a[low, mid]、a[mid+1, high]归并后的结果
int i = low, j = mid+1, k = 0;
/*
i:遍历第一个子表
j:遍历第二个子表
k:作为tmpa的索引
*/
tmpa = (int *)malloc((high-low+1) * sizeof(int));
//在第一个子表和第二个子表均未扫描完时循环
while (i <= mid && j <= high)
if (a[i] <= a[j]) //tmpa[k]保存较小的一个元素
{
tmpa[k] = a[i];
i++; k++;
}
else
{
tmpa[k] = a[j];
j++; k++;
}
//判断是否在某个子表还未扫描完时,另一个子表就已经扫描完了
//出现这样的情况的话,需要把未扫描完的子表的未扫描的元素直接添加到tmpa数组中
while (i <= mid)
{
tmpa[k] = a[i];
k++; i++;
}
while (j <= high)
{
tmpa[k] = a[j];
k++; j++;
}
//将tmpa复制回a中
for (k = 0, i = low; i <= high; i++, k++)
a[i] = tmpa[k];
free(tmpa);
}
void MergePass(int a[], int length, int n)//归并长度为length时,所有无序列表中的两两相邻子表
{
int i;
for (i = 0; i+2*length-1 < n; i = i + 2*length)
Merge(a, i, i+length-1, i+2*length-1);
/*
i为当前两两归并的第一个无序序列中的第一个元素
i+length-1为当前两两归并的第一个无序序列中的最后一个元素
i+2*length-1当前两两归并的第二个无序序列中的最后一个元素
*/
if(i+length-1 < n)//用于最后一趟时,对余下的两个长度不相等(后者的长度小于最后一趟时length的值)的子表进行归并
Merge(a, i, i+length-1, n-1);
}
void MergeSort(int a[], int n) //二路归并算法
{
int length;
for (length = 1; length < n; length = 2 * length)
MergePass(a, length, n);
}
void display(int a[], int n)
{
for (int i = 0; i < n; i++)
cout << a[i] << " ";
cout << endl;
}
int main()
{
int n = 10;
int a[] = {2,5,1,7,10,6,9,4,3,8};
cout << "排序前:";
display(a, n);
MergeSort(a, n);
cout << "排序后:";
display(a, n);
return 0;
}
思想像胡须,不成熟就不可能长出来。——伏尔泰
我可以理解为我的这道题的“胡须”长出来了吗。
自顶向下的二路归并排序
- 这是典型的二分法算法:将当前序列一分为二,对这两个子序列再分别一分为二(递归),直至子序列的长度为1或0(因为一个元素的子表或者空表可以看成有序表);对上面每次得到的子序列进行排序,最后一轮的子序列的排序结果一层层返回,得到更多元素的排序,最终使得所有元素都参与了排序。(P91图3.5)
- 不再需要MergePass()函数。只需要MergeSort()、Merge()函数。这就是利用到递归技术的好处——自动回退!且Merge()函数不需要任何改变,只是把MergeSort()函数变成了递归。
#include<cstdio>
#include<stdlib.h>
#include<iostream>
using namespace std;
void Merge(int a[], int low, int mid, int high)
/*对a[low, mid]、a[mid+1, high]这两个相邻序的有序列进行归并*/
{
int *tmpa; //用于保存a[low, mid]、a[mid+1, high]归并后的结果
int i = low, j = mid+1, k = 0;
/*
i:遍历第一个子表
j:遍历第二个子表
k:作为tmpa的索引
*/
tmpa = (int *)malloc((high-low+1) * sizeof(int));
//在第一个子表和第二个子表均未扫描完时循环
while (i <= mid && j <= high)
if (a[i] <= a[j]) //tmpa[k]保存较小的一个元素
{
tmpa[k] = a[i];
i++; k++;
}
else
{
tmpa[k] = a[j];
j++; k++;
}
//判断是否在某个子表还未扫描完时,另一个子表就已经扫描完了
//出现这样的情况的话,需要把未扫描完的子表的未扫描的元素直接添加到tmpa数组中
while (i <= mid)
{
tmpa[k] = a[i];
k++; i++;
}
while (j <= high)
{
tmpa[k] = a[j];
k++; j++;
}
//将tmpa复制回a中
for (k = 0, i = low; i <= high; i++, k++)
a[i] = tmpa[k];
free(tmpa);
}
void MergeSort(int a[], int low, int high) //二路归并算法
{
int mid;
if (low < high)
{
mid = (low + high) / 2;
MergeSort(a, low, mid);
MergeSort(a, mid+1, high);
Merge(a, low, mid, high);
}
}
void display(int a[], int n)
{
for (int i = 0; i < n; i++)
cout << a[i] << " ";
cout << endl;
}
int main()
{
int n = 10;
int a[] = {2,5,1,7,10,6,9,4,3,8};
cout << "排序前:";
display(a, n);
MergeSort(a, 0, n-1);
cout << "排序后:";
display(a, n);
return 0;
}