算法课的作业,记录写作业时的思路以便于期末复习。
注意:move、compare变量是为了完成作业,用于记录不同排序算法的腾挪次数和比较次数,初始值均为0,实际排序不需要用到。
插入排序
思路:将第一个元素作为初始的有序表,依次插入后续元素直到结束。
void insert_sort(int R[], int n){
for(int i = 1; i < n; i++){ //遍历待插入元素
int temp = R[i];
int j = i - 1;
while (j >= 0 && temp < R[j]) { //遍历有序表
R[j+1] = R[j];
R[j] = temp;
j--;
}
}
}
记录腾挪次数和比较次数版:
void insert_sort(int R[], int n){
//int flag = 1;
for(int i = 1; i < n; i++){ //遍历待插入元素
int flag = 1;
int temp = R[i];
int j = i - 1;
while (j >= 0 && temp < R[j]) { //遍历有序表
flag = 0;
compare++; move++;
R[j+1] = R[j];
R[j] = temp;
j--;
}
if(flag) compare++; //未进入循环也比较了一次,但是没有腾挪
}
}
代码解释:将数组分为排序表和待插入两部分。排序表就是我们要输出的最终结果,当待插入元素个数为0排序表也就是最终结果。i记录待插入元素,由于0是第一个元素直接作为排序表的第一个元素,故从i=1开始作为第一个待插入元素。
需要用一个临时变量temp保存待插入元素R[i]。j用来从后往前遍历有序表中的元素将其与待插入元素比较,如果待插入元素temp比当前有序表的R[j]元素小,就将R[j]元素和待插入元素temp换个位置,直到待插入元素到达正确的位置。
算法分析:
1.空间复杂度:只需要一个元素空间temp来进行位置交换。
2.时间复杂度:外层需要n-1次循环遍历待插入元素,每次插入最少比较一次,最多比较i次。时间复杂度为O(n^2)
根据上述分析,可知:
最好情况:数组本身就是有序数组,也就是说只需要进行比较操作,不用进行腾挪。compare操作次数为n-1,腾挪次数为0
最差情况:数组时降序排序的,这种情况下compare和move次数相同均为n(n-1)/2
平均情况:
3.稳定性:如果碰见一个和插入元素相等的,那么插入元素把想插入的元素放在相等元素的后面。所以,相等元素的前后顺序没有改变,从原无序序列出去的顺序就是排好序后的顺序,所以插入排序是稳定的。
归并排序
这里略去自顶向下的递归方法,直接考虑自底向上的非递归算法。
参考自:https://www.jianshu.com/p/c02c521c8223
思路:首先将长度为1的n个数组相邻元素两两配对,构成了长度为2的n/2个数组,合并时用比较算法对这每个子数组中元素进行排序;再将这些长度为2的n/2个数组两两合并,构成了长度为4,个数为n/4的子数组,合并时用比较算法对每个子数组元素排序。继续直到形成长度为n,子数组个数=1的整个数组为止。
void merge(int c[], int d[], int left, int mid, int right){
int i = left, j = mid + 1, k = left; //i、j左右序列指针
while(i <= mid && j <= right){
if (c[i] <= c[j]) d[k++] = c[i++];
else d[k++] = c[j++];
}
if(i > mid) //左序列结束,将右序列元素依次接在结果后面
for(int q = j; q <= right; q++)
d[k++] = c[q];
else
for(int q = i; q <= mid; q++)
d[k++] = c[q];
}
void merge_pass(int x[], int y[], int s, int n){
int i = 0;
while(i <= n - 2 * s){
merge(x, y, i, i+s-1, i+2*s-1);
i = i + 2 * s;
}
if(i + s < n)
merge(x, y, i, i+s-1, n-1);
else
for (int j = i; j <= n - 1; j++)
y[j] = x[j];
}
void merge_sort(int a[], int n){
int *b = new int[n];
int s = 1; //每次s个元素归并
while(s < n){
merge_pass(a, b, s, n);
s += s;
merge_pass(b, a, s, n);
s += s;
}
}
后续补充。。
快速排序
int partition(int arr[], int p, int r){
int i = p, j = r + 1;
int x = arr[p];
int temp = 0;
while(1){
// 从左往右找第一个比x大的
while(arr[++i] < x && i < r) compare_3++;
// 从右往左找第一个比x小的
while(arr[--j] > x) compare_3++;
if(i >= j)
break;
temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
move_3 += 3;
}
arr[p] = arr[j];
arr[j] = x;
move_3 += 2;
return j;
}
void quick_sort(int arr[], int p, int r){
if(p < r){
int q = partition(arr, p, r);
// int q = stable_partition(arr, p, r);
quick_sort(arr, p, q - 1);
quick_sort(arr, q + 1, r);
}
}
代码解释:
算法分析:
后续补充。。。
更改partition函数,进化为稳定的快速排序:
int stable_partition(int arr[], int p, int r){
int i = p, j = r + 1;
int x = arr[p];
int *b = new int[r + 1];
int bl = 0, br = r;
int start = p;
while(1){
// 从左往右找第一个比x大的
while(arr[++i] < x && i < r){
compare_2++;
}
// 从右往左找第一个比x小的
while(arr[--j] > x) {
compare_2++;
}
if(i >= j)
break;
b[bl++] = arr[i];
b[br--] = arr[j];
}
for(int k = br + 1; k <= r; k ++){
arr[start++] = b[k];
}
int index = start;
arr[start++] = x;
for(int m = p; m < bl; m++){
arr[start++] = b[m];
}
return index;
}
思路:添加一个相同长度的辅助数组,在从右往左扫描和从左往右扫描到比基准元素大或小的元素时不是交换他们的位置,而是将其按序存到辅助数组的最左端和左右端。然后再按序将辅助数组右端的元素、基准元素、辅助数组左端的元素放入数组中,这样就保证了相同大小在前面的元素排序后仍然在前面。
运行结果: