排序算法及其应用(C++实现)
一、 常见排序算法:
冒泡排序、选择排序、插入排序、归并排序、快速排序、堆排序…
冒泡排序:
从第0号元素到第n-1号元素遍历,若前面一个元素大于后面一个元素,则交换两个元素,这样可将整个序列中最大的元素冒泡到最后。然后再从第0号到第n-2号遍历,如此往复,直到只剩一个元素。
void bubbleSort(int a[], int n){
for(int i = 0; i < n; ++i){
for(int j = 0; j < n-i-1; ++j){
if(a[j] > a[j+1])
swap(a[j], a[j+1]);
}
}
}
选择排序:
从全部序列中选取最小的,与第0个元素交换,然后从第1个元素往后找出最小的,与第一个元素交换,再从第2个元素往后选取最小的,与第2个元素交换,直到选取最后一个元素。
void selectSort(int a[], int n){
for(int i = 0; i < n; ++i){
int minIndex = i;
for(int j = i+1; j < n; ++j){
if(a[j] < a[minIndex])
minIndex = j;
}
swap(a[minIndex], a[i]);
}
}
插入排序:
思路类似扑克牌的排序,每次从未排序序列的第一个元素,插入到已排序序列中的合适位置。假设初始的有序序列为第0个元素(本文描述的序号都从0开始),只有一个元素的序列肯定是有序的,然后从原先序列的第1个元素开始到第n-1个元素遍历,每次将当前元素插入到它之前序列中的合适位置。
void insertSort(int a[], int n){
for(int i = 1; i < n; ++i){
int val = a[i];
int j;
for(j = i - 1; j >= 0 && a[j] > val; --j)
a[j+1] = a[j];
a[j+1] = val;
}
}
归并排序:
归并排序的思想是,利用二分的特性,将序列分成两个子序列进行排序,将排序后的两个子序列归并(合并),当序列的长度为2时,它的两个子序列长度为1,即视为有序,可直接合并,即达到归并排序的最小子状态。
void mergeSort_Recursive(int a[], int b[], int start, int end){
if(start >= end)
return;
int mid = start + (end - start)/2;
int start1 = start, end1 = mid;
int start2 = mid + 1, end2 = end;
mergeSort_Recursive(a, b, start1, end1);
mergeSort_Recursive(a, b, start2, end2);
int i = start;
while(start1 <= end1 && start2 <= end2)
b[i++] = a[start1] < a[start2]? a[start1++] : a[start2++];
while(start1 <= end1)
b[i++] = a[start1++];
while(start2 <= end2)
b[i++] = a[start2++];
for(int i = start; i <= end; ++i)
a[i] = b[i];
}
void mergeSort(int a[], int n){
int* b = new int[n];
mergeSort_Recursive(a, b, 0, n-1);
delete[] b;
}
快速排序:
快排的思想是,选取第一个数为基准,通过一次遍历将小于它的元素放到它的左侧,将大于它的元素放到它的右侧,然后对它的左右两个子序列分别递归地执行同样的操作。总之,快速排序利用分而治之的思想。
int partition(int a[], int left, int right){
int pivot = a[left];
int i = left;
for(int j = left; j < right; ++j){
if(a[j] < pivot){
swap(a[++i], a[j]);
}
}
swap(a[i], a[left]);
return i;
}
void quickSort(int a[], int left, int right){ //左闭右开
if(left < right){
int i = left + rand() % (right - left); //将主元放在首位置
swap(a[left], a[i]);
int p = partition(a, left, right);
quickSort(a, left, p);
quickSort(a, p + 1, right);
}
}
堆排序:
堆排序利用的是二叉树的思想,所谓堆就是一个完全二叉树。堆排序分两个流程,首先是构建大顶堆,然后是从大顶堆中获取按逆序提取元素。首先是大顶堆,大顶堆即一个完全二叉树,每一个节点都大于它的所有子节点。大顶堆可以按照从上到下从左到右的顺序,用数组来存储,第i个节点的父节点序号为(i-1)/2,左子节点序号为2i+1,右子节点序号为2(i+1)
void heapify(int a[], int node, int size){
int left = 2*node + 1;
int right = 2*(node + 1);
int max = node;
if(left < size && a[left] > a[max])
max = left;
if(right < size && a[right] > a[max])
max = right;
if(max != node){
swap(a[max], a[node]);
heapify(a, max, size); //使交换后的子子树也满足最大堆
}
}
void heapSort(int a[], int n){
for(int i = (n-1)/2; i >= 0; --i) //最后一个叶子节点的父节点为(n-1)/2
heapify(a, i, n); //构造最大堆
for(int i = n-1; i >= 0; --i){
swap(a[0], a[i]); //将当前最大数放置在数组末尾
heapify(a, 0, i); //将新产生的未排序部分继续进行堆排序
}
}
算法测试:
int main(){
int a[8] = {2,1,5,4,3,6,8,7};
int n = sizeof(a)/sizeof(a[0]);
cout << "before sorting:\n";
for(int i = 0; i < n; ++i)
cout << a[i] << " ";
cout << "\n";
//bubbleSort(a, n);
//selectSort(a, n);
//insertSort(a, n);
//mergeSort(a, n);
//quickSort(a, 0, n);
heapSort(a, n);
cout << "after Sorting:\n";
for(int i = 0; i < n; ++i)
cout << a[i] << " ";
cout << "\n";
return 0;
}
二、各类排序算法算法复杂度:
算法 | 时间复杂度 | 空间复杂度 | 稳定性 |
---|---|---|---|
冒泡排序 | 双层循环,O(n2) | 不需要额外空间, O(1) | 稳定 |
选择排序 | 双层循环,O(n2) | 不需要额外空间, O(1) | 不稳定 |
插入排序 | 双层循环,O(n2) | 不需要额外空间, O(1) | 稳定 |
归并排序 | 时间复杂度O(nlogn) | 需要额外空间, O(n) | 稳定 |
快速排序 | 时间复杂度O(nlogn) | 不需要额外空间, O(1) | 不稳定 |
堆排序 | 时间复杂度O(nlogn) | 不需要额外空间, O(1) | 不稳定 |
三、 各类排序算法适用情况:
(1)当数据规模较小时候,可以使用插入排序或者选择排序。
(2)当文件的初态已经基本有序,可以用插入排序和冒泡排序。
(3)当数据规模较大时,应用速度最快的排序算法,可以考虑使用快速排序。
(4)要求排序时是稳定的,可以考虑用归并排序。
快速排序是目前基于比较的内部排序中被认为是最好的方法,当待排序的关键字是随机分布时,快速排序的平均时间最短;堆排序所需的辅助空间少于快速排序,并且不会出现快速排序可能出现的最坏情况。这两种排序都是不稳定的。若要求排序稳定,则可选用归并排序。