1 交换排序
1.1 冒泡排序
从下往上(从后往前)比较,如果发现下面的比上面的小(轻),则交换二者的位置。利用哨兵(见注释部分),某趟没有任何交换则表明所有都排好序,退出。稳定排序,时间复杂度为0(n^2)。
// 冒泡排序, 升序排列
void Swap(int& a, int& b)
{
int t = a;
a = b;
b = t;
}
void BubbleSort(int arrary[], int len)
{
// bool exchange;
for (int i=0; i<len-1; i++) // 最多做n-1趟排序
{
// exchange = false;
for (int j=len-1; j>i; j--)
{
if (arrary[j] < arrary[j-1])
{
// exchange = true;
Swap(arrary[j], arrary[j-1]);
}
}
// if (!exchange)
// {
// break;
// }
}
}
1.2 快速排序
它采用了一种分治的策略,通常称其为分治法。分治法的基本思想是:将原问题分解为若干个规模更小但结构与原问题相似的子问题。递归地解这些子问题,然后将这些子问题的解组合为原问题的解。
在数组中选中一个记录作为基准,将数组分成左右两个子区间,使得左边的值小于等于基准,右边的值大于等于基准。递归调用快速排序对左右两个子区间进行排序。不稳定排序,时间复杂度0(n*log(n))。
int Partition(int arr[], int low, int high)
{
int pivot = arr[low];
while (low < high)
{
while (low < high && arr[high] >= pivot)
--high;
if (low != high)
Swap(arr[low], arr[high]);
while (low < high && arr[low] <= pivot)
++low;
if (low != high)
Swap(arr[low], arr[high]);
}
return low;
}
void QuickSort(int arr[], int low, int high)
{
if (low < high)
{
int n = Partition(arr, low, high);
QuickSort(arr, low, n);
QuickSort(arr, n+1, high);
}
}
2 插入排序
2.1 直接插入排序
每次将一个待排序的记录,按其关键字大小插入到前面已经排好序的子序中的适当位置,直到全部记录插入完成为止。稳定排序,时间复杂度0(n^2)。
void InsertSort(int arr[], int len)
{
int j;
int tmp;
for (int i=1; i<len; i++)
{
if (arr[i] < arr[i-1])
{
tmp = arr[i];
j = i-1;
do
{
arr[j+1] = arr[j];
j --;
} while (j>=0 && arr[j]>tmp);
arr[j+1] = tmp;
}
}
}
2.2 希尔排序
希尔排序是对插入排序的改进,它每次排序把序列的元素按照某个增量分成几个子序列,对这几个子序列进行插入排序,然后不断的缩小增量扩大每个子序列的元素数量,直到增量为1的时候子序列就和原先的待排列序列一样了,此时只需要做少量的比较和移动就可以完成对序列的排序了。不稳定排序,时间复杂度0(n^(1+m))。注:0<m<1。
// 增量从数组长度的一半开始,每次减小一倍
void ShellSort(int arr[], int len)
{
int temp;
for (int d = len/2; d>0; d /= 2)
{
for (int i=d; i<len; i++)
{
temp = arr[i];
for (int j=i; j>=d; j-=d)
{
if (temp < arr[j-d])
{
arr[j] = arr[j-d];
}
else
{
break;
}
}
arr[j] = temp;
}
}
}
3 选择排序
3.1 直接选择排序
每一趟从待排序的记录中选出关键字最小的记录,顺序放在已排好序的子文件的最后,直到全部记录排序完毕。非稳定排序,时间复杂度为0(n^2)。
void SelectSort(int arr[], int len)
{
int k;
for (int i=0; i<len; i++)
{
k = i;
for (int j=i+1; j<len; j++)
{
if (arr[i] > arr[j])
{
k = j;
}
}
if (k != i)
{
Swap(arr[i], arr[k]);
}
}
}
3.2 堆排序
n个关键字序列Kl,K2,…,Kn称为堆(Heap),当且仅当该序列满足如下性质(简称为堆性质):Ki≤K2i且ki≤K2i+1 或 Ki≥K2i且Ki≥K2i+1(1≤i≤ n)。注若索引从0开始,则是:Ki≤K2i+1且Ki≤K2i+2 或 Ki≥K2i+1且Ki≥K2i+2(0≤i≤ n-1)。节点i的父节点为floor(i/2)或floor((i-1)/2)。
若将此序列所存储的向量R[1..n]看做是一棵完全二叉树的存储结构,则堆实质上是满足如下性质的完全二叉树:树中任一非叶结点的关键字均不大于(或不小于)其左右孩子(若存在)结点的关键字。 (即如果按照线性存储该树,可得到一个不下降序列或不上升序列)。不稳定排序,时间复杂度0(nlogn)。
// start表示数组从0或1开始
void MaxHeapify(int arr[], int start, int end, int i)
{
int lChild = (start==0) ? (2*i+1) : (2*i);
int rChild = (start==0) ? (2*i+2) : (2*i+1);
int larger = i;
if (lChild<=end && arr[lChild]>arr[i])
{
larger = lChild;
}
if (rChild<=end && arr[rChild]>arr[larger])
{
larger = rChild;
}
if (larger != i)
{
Swap(arr[larger], arr[i]);
MaxHeapify(arr, start, end, larger);
}
}
void BuildMaxHeap(int arr[], int start, int end)
{
int i = (start==0) ? ((end-1)/2) : (end/2);
for (; i>=start; i--)
{
MaxHeapify(arr, start, end, i);
}
}
// 堆排序
void HeapSort(int arr[], int start, int end)
{
// build heap
BuildMaxHeap(arr, start, end);
// 依次将第一个与最后一个交换,然后保持堆性质
// 这样就得到了升序排列
while(end>start)
{
Swap(arr[start], arr[end]);
end--;
MaxHeapify(arr, start, end, start);
}
}
4 归并排序
把待排序序列分成相同大小的两个部分,依次对这两部分进行归并排序,完毕之后再按照顺序进行合并。稳定排序,时间复杂度0(nlogn)。
void Merge(int arr[], int start, int mid, int end)
{
int n1 = mid-start+1;
int n2 = end-mid;
int* arr1 = new int[n1+1];
int* arr2 = new int[n2+1];
// 设置最后一个值为int最大值
arr1[n1] = 2147483647;
arr2[n2] = 2147483647;
int i,j,k;
for (i=0; i<n1; i++)
{
arr1[i] = arr[start+i];
}
for (i=0; i<n2; i++)
{
arr2[i] = arr[mid+i+1];
}
for (k=start,i=0,j=0; k<=end; k++)
{
if (arr1[i] <= arr2[j])
{
arr[k] = arr1[i++];
}
else
{
arr[k] = arr2[j++];
}
}
delete arr1;
delete arr2;
}
void MergeSort(int arr[], int start, int end)
{
if (start < end)
{
int mid = (start+end)/2;
MergeSort(arr, start, mid);
MergeSort(arr, mid+1, end);
Merge(arr, start, mid, end);
}
}