前言
1. 排序的稳定性
对于一个排序,如果排序前a在b的前面,且排序对象,比如说a和b的分数是一样的,如果排序完a和b的顺序没有发生变化,则说明该排序是稳定排序,倘若发生了变化,则说明该排序是不稳定的
2. 内外排序
内排序是在整个排序过程中,待排序对象都放置在内存中。外排序则是由于数据过多要在内外存之间多次交换数据才能进行。
对于内排序而言,算法的性能主要受三方面影响:
- 时间性能
- 辅助空间
- 算法复杂度
3. 排序用到的结构和函数
typedef struct{
int r[MAXSIZE+1];
int length;
}SqList;
void swap(SqList &L, int i, int j){
int temp = L.r[i];
L.r[i] = L.r[j];
L.r[j] = temp;
}
一、冒泡排序
冒泡排序的基本思想就是不断对比相邻两个数,如果左边的比右边的大,那我们就交换他们,做完一轮后,由于最小的一定以及出现在整个数组的左边了,这时我们只需要对剩下的元素继续进行冒泡排序就好,该算法的时间复杂度是O(n2)。
void BubbleSort(SqList &L){
for(int i = 1; i <= L.length; ++i){
for(int j = L.length-1; j >= i; ++j){
if(L.r[j] > L.r[j+1]) swap(L, j, j+1);
}
}
}
如果出现后面所有数都已经拍好的情况,经典冒泡排序还是会跑完整个流程,这个时候我们引入一个flag,如果发现某一趟冒泡排序没有进行交换,那么flag就会被设置未FALSE并且退出循环。
void BubbleSort(SqList &L){
bool flag = TRUE;
for(int i = 1; i <= L.length && flag; ++i){
for(int j = L.length-1; j >= i; ++j){
flag = FALSE:
if(L.r[j] > L.r[j+1]) {
swap(L, j, j+1);
flag = TRUE;
}
}
}
二、简单选择排序
简单插入排序的基本思想是不断遍历未排序部分,找出其中最小的值把他放到对应的位置上,算法复杂度是O(n2).
void SelectSort(SqList &L){
for(int i = 0; i <= L.length; ++i){
min = i;
for(int j = i + 1; j <= L.length, ++j){
if(L.r[min] > L.r[j]) min = j;
}
if(i != min) swap(L, i, min);
}
}
三、直接插入排序
直接插入排序类似于我们玩扑克牌时,整理牌的时候,直接把他插入到它的位置,对于代码,从数组的第二个位置开始,依次向后做,如果当前位置的值小于左边的,就将他放到L.r[0],然后依次把前面的元素向后覆盖,直到找到他的位置,该算法的时间复杂度是O(n2)。
void InsertSort(SqList &L){
for(int i = 2; i <= L.length; ++i){
if(L.r[i] < L.r[i-1]){
L.r[0] = L.r[i];
for(int j = i -1; L.r[j] > L.r[0]; --j){
L.r[j+1] = L.r[j];
}
L.r[j] = L.r[0];
}
}
}
四、希尔排序
希尔排序的核心思想是将数组分组,在每个组内进行直接插入排序,d是同组元素在数组中的间隔。
void ShellSort(SqList &L){
int d = L.length;
while(d >= 1){
d /= 2;
for(int i = d + 1; i <= L.length; ++i){
if(L.r[i] < L.r[i-d]){
L.r[0] = L.r[i];
for(int j = i-d; j > 0 && L.r[0] < L.r[j]; j -= d)
L.r[j+d] = L.r[j];
L.r[j+d] = L.r[0]
}
}
}
}
五、堆排序
堆排序主要使用完全二叉树,不断地找出未排序数组中最大的元素把他放到合适的位置上,代码详解看这里
void HeapHeadAdjust(SqList &L, int s, int m){
// s是要存放根的位置,m是是待排序的元素个数
int L.r[0] = L.r[s];
for(int j = 2*s; j < m; j *= 2){
if(L.r[j] < L.r[j+1]) ++j; // j指向较大的元素
if(L.r[0] >= L.r[j]) break;
L.r[s] = L.r[j];
s = j;
}
L.r[s] = L.r[0]
}
void HeapSort(SqList &L){
for(int i = L.length / 2; i > 0; ++i)
HeapHeadAdjust(L, i, L.length);
for(int i = L.length; i > 1; ++i){
swap(L, i, 1);
HeapHeadAdjust(L, 1, i-1);
}
}
六、归并排序
归并排序首先将数组拆分成单个元素,再逐层往上合并,merge函数做的就是先将数组复制出去,然后逐项对比要合并的两组的大小,依次将小的数放回原来的数组,代码详解看这里
int temp[L.length];
void merge(SqList &L, int low, int mid, int high){
for(int k = low; k < high; ++k){
temp[k] = L[k];
}
for(int i = low, j = mid + 1, k = low; i <= mid && j <= high; ++k){
if(temp[i] < temp[j]) L[k] = temp[i++];
else L[k] = temp[j++];
}
while(i <= low) L[k++] = temp[i++];
while(j <= high) L[k++] = temp[j++];
}
void MergeSort(SqList &L, int low, int high){
int mid = 0;
while(low < high){
mid = (low + high) / 2;
MergeSort(L, low, mid);
MergeSort(L, mid + 1, high);
merge(L, low, mid, high);
}
}
七、快速排序
快速排序使用到了枢轴,首先我们先找到这个枢轴应该在的位置,通过两个游标一个在左一个在右,不断交换元素,确保枢轴在插入时左边所有元素都比他小,右边所有元素都比他大,代码详解看这里
int Partiton(SqList &L, int low, int high){
int pivotkey = L.r[low];
while(low < high){
while(L.r[high] > pivotkey) high--;
L.r[low] = L.r[high];
while(L.r[low] < pivotkey) low--;
L.r[high] = L.r[low];
}
L.r[low] = pivotkey;
return low;
}
void QuickSort(SqList &L, int low, int high){
if(low < high){
int pivot = Partition(L, low, high);
QuickSort(L, low, pivot-1);
QuickSort(L, pivot+1, high);
}
}
对于快速排序可以做出优化,优化小数组时的排序方案,快速排序不适合处理元素个数较少的数组,因而当数组元素个数少于一个阈值时,我们可以用直接插入排序来代替
void QuickSort(SqList &L, int low, int high){
if((high - low) > MAX_LENGTH_INSERT_SORT){
int pivot = Partition(L, low, high);
QuickSort(L, low, pivot-1);
QuickSort(L, pivot+1, high);
}else
InsertSort(L);
}