各种排序算法
(一)排序基本概念:
根据排序过程中待排序文件存放的位置不同,可以把排序分为内部和外部排序两大类。在排序过程中,所有需要排序的数都在内存,并在内存中调整它们的存储顺序,称为内排序;在排序过程中,只有部分数被调入内存,并借助内存调整数在外存中的存放顺序排序方法称为外排序。内部排序适用于记录个数不很多的较小待排序文件的排序;外部排序则适用于记录个数太多不能一次全部放入内存的较大待排序文件的排序。
(二)内部排序分类:
- 交换排序:常用的交换排序方法有冒泡排序和快速排序。
- 选择排序:常用的选择排序方法有直接选择排序、树型选择排序和堆排序。
- 插入排序:主要的插入排序方法有直接插入排序、希尔排序、二分法插入排序、二路插入排序和共享栈插入排序等。
- 归并排序
- 基数排序
(三)内部排序方法比较
1.时间性能比较
二路归并排序、堆排序和快速排序O(nlog2n),
希尔排序O(n1.5),
插入、冒泡、选择复杂度O(n2)
2.辅助空间的比较
二路归并排序的辅助空间为O(n),其他排序的辅助空间为O(1).
3.稳定性比较
插入排序、冒泡排序、二叉树排序、二路归并排序是稳定的。
选择排序、希尔排序、快排、堆排序是不稳定的。
4. 其它比较
插入、冒泡排序的速度较慢,但参加排序的序列局部或整体有序时,这种排序能达到较快的速度。反而在这种情况下,快速排序反而慢了。
当n较小时,对稳定性不作要求时宜用选择排序,对稳定性有要求时宜用插入或冒泡排序。
若待排序的记录的关键字在一个明显有限范围内时,且空间允许是用桶排序。
当n较大时,关键字元素比较随机,对稳定性没要求宜用快速排序。
当n较大时,关键字元素可能出现本身是有序的,对稳定性有要求时,空间允许的情况下,宜用归并排序。
当n较大时,关键字元素可能出现本身是有序的,对稳定性没有要求时宜用堆排序。
下面具体给出各种排序的代码。前提:我把num[]设为全局变量了
冒泡排序
void bubblesort(int num[], int n) { for(int i = 0; i < n - 1; i ++){ for(int j = 0; j < n - 1 - i; j ++){ if(num[j] > num[j + 1]){ swap(num[j], num[j + 1]); } } } }
快速排序
快排:首先说明一下快速排序是对冒泡排序的改进。冒泡排序是把序列分成了两部分,前半部分无序,后半部分升序排列,并且后半部分的数都大于前半部的数。由此可得到快速排序和冒泡排序的一些共同点:1.都要经历n趟排序;2.每趟排序要经历O(n)次比较;3.都是后半部分元素比前半部。而不同之处就在于冒泡排序的交换操作发生相邻的元素之间,即一趟排序可以要经过多次交换操作;快速排序的交换操作发生在间隔比较远的两个元素之间,一趟排序要经过交换操作次数会少一些。
快排(递归)
int partition(int num[], int low, int high) { int i = low; int j = high; while(i < j){ while(num[i] < num[j]){ j --; } if(i < j){ swap(num[i], num[j]); i ++; } while(num[i] < num[j]){ i ++; } if(i < j){ swap(num[i], num[j]); j --; } } return i; } void quicksort(int num[], int low, int high) { if(low < high){ int mid = partition(num, low, high); quicksort(num, low, mid - 1); quicksort(num, mid + 1, high); } }
快排(非递归)
queue<int> q1, q2; //用队列实现 void quicksort(int num[], int low, int high) { q1.push(low); q2.push(high); while(!q1.empty()){ int x = q1.front(); q1.pop(); int y = q2.front(); q2.pop(); //print(num, high - low + 1); //cout << "x:" << x << " y:" << y << endl; int j = x; for(int i = x; i < y; i ++){ if(num[i] < num[y]){ swap(num[i], num[j]); j ++; } } swap(num[j], num[y]); //cout << "j:" << j << endl; if(x < j - 1){ q1.push(x); q2.push(j - 1); } if(j + 1 < y){ q1.push(j + 1); q2.push(y); } } } //用栈实现 stack<int> s1, s2; void quicksort2(int num[], int low, int high) { s1.push(low); s2.push(high); while(!s1.empty()){ int x = s1.top(); s1.pop(); int y = s2.top(); s2.pop(); int j = x; for(int i = x; i < y; i ++){ if(num[i] < num[y]){ swap(num[i], num[j]); j ++; } } swap(num[j], num[y]); if(x < j - 1){ s1.push(x); s2.push(j - 1); } if(j + 1 < y){ s1.push(j + 1); s2.push(y); } } }
快排的非递归的算法比递归实现还要慢。下面解释为什么会这样?快排的递归算法使用的栈由程序自动产生,栈中包含:函数调用时的参数和函数中的局部变量。如果局部变量很多或者函数内部又调用了其他函数,则栈会很大。每次递归调用都要操作很大的栈,效率自然会下降。而对于非递归算法,每次循环使用自己预先创建的栈,因此不管程序复杂度如何,都不会影响程序效率。对于上面的快速排序,由于局部变量只有一个mid,栈很小,所以效率并不比非递归实现的低。
选择排序
void selectsort(int num[], int n) { int index; for(int i = 0; i < n - 1; i ++){ index = i; for(int j = i + 1; j < n; j ++){ if(num[j] < num[i]){ index = j; } } if(i != index){ swap(num[i], num[index]); } } }
堆排序
void max_heapify(int num[], int i, int last) { int index; if(2 * i + 2 <= last){ if(num[2 * i + 1] < num[2 * i + 2]){ index = 2 * i + 2; } else{ index = 2 * i + 1; } if(num[i] < num[index]){ swap(num[i], num[index]); max_heapify(num, index, last); } } else if(2 * i + 1 <= last){ if(num[2 * i + 1] > num[i]){ swap(num[i], num[2 * i + 1]); } } } void build_maxheap(int num[], int n) { for(int i = n - 1; i >= 0; i --){ max_heapify(num, i, n - 1); } } void heapsort(int num[], int n) { build_maxheap(num, n); for(int i = n - 1; i > 0; i --){ swap(num[0], num[i]); max_heapify(num, 0, i - 1); } }
插入排序
void insertsort(int num[], int n) { for(int i = 1; i < n; i ++){ int tmp = num[i]; int j = i - 1; while(j >= 0 && tmp < num[j]){ num[j + 1] = num[j]; j --; } num[j + 1] = tmp; } }
希尔排序
void shellsort(int n)
{
int k = n / 2;
while(k >= 1){
for(int j = k; j < n; j ++){
int t = num[j];
int i = j - k;
while(i >= 0 && num[i] > t){
num[i + k] = num[i];
i = i - k;
}
num[i + k] = t;
}
k /= 2;
}
}
归并排序
归并排序(递归)
int merge(int start, int mid, int end)
{
int num1[mid - start + 1];
int num2[end - mid];
int i = start;
int j = mid + 1;
while(i <= mid){
num1[i - start] = num[i];
i ++;
}
while(j <= end){
num2[j - mid - 1] = num[j];
j ++;
}
i = 0;
j = 0;
int k = start;
while(i < mid - start + 1 && j < end - mid){
if(num1[i] < num2[j]){
num[k ++] = num1[i];
i ++;
}
else{
num[k ++] = num2[j];
j ++;
}
}
while(i < mid - start + 1){
num[k ++] = num1[i ++];
}
while(j < end - mid){
num[k ++] = num2[j ++];
}
}
void mergesort(int start, int end)
{
if(start < end){
int mid = (start + end) / 2;
mergesort(start, mid);
mergesort(mid + 1, end);
merge(start, mid, end);
}
return ;
}
归并排序(非递归)
void merge(int start, int mid, int end)
{
int len1 = mid - start + 1;
int len2 = end - mid;
int num1[len1], num2[len2];
int i = start;
int j = mid + 1;
while(i <= mid){
num1[i - start] = num[i];
i ++;
}
while(j <= end){
num2[j - mid - 1] = num[j];
j ++;
}
i = 0;
j = 0;
int k = start;
while(i < mid - start + 1 && j < end - mid){
if(num1[i] < num2[j]){
num[k ++] = num1[i ++];
}
else{
num[k ++] = num2[j ++];
}
}
while(i < mid - start + 1){
num[k ++] = num1[i ++];
}
while(j < end - mid){
num[k ++] = num2[j ++];
}
}
void mergesort(int start, int end)
{
int x, y, mid;
int head = 0;
int tail = 0;
int sl[end], sh[end];
sl[head] = start;
sh[head] = end;
while(head <= tail){
x = sl[head];
y = sh[head];
head ++;
mid = (x + y) / 2;
if(x < mid){
tail ++;
sl[tail] = x;
sh[tail] = mid;
}
if(mid + 1 < y){
tail ++;
sl[tail] = mid + 1;
sh[tail] = y;
}
}
while(tail >= 0){
x = sl[tail];
y = sh[tail];
tail --;
mid = (x + y) / 2;
merge(x, mid, y);
}
}