比较排序:通过元素间的比较进行排序,时间复杂度不能超过O(nlogn),不在乎数据规模和分布情况。包括:交换(冒泡,快排)、插入(简单插入,希尔)、选择(简单选择,堆排序)、归并排序。
非比较排序:不通过元素比较进行排序,时间复杂度O(n),但需要空间确定位置,对数据规模与分布有要求。包括:基数排序、计数排序、桶排序。
排序方法 | 时间复杂度(平均) | 时间复杂度(最好) | 时间复杂度(最坏) | 空间复杂度 | 稳定性 |
冒泡排序 | O(n2) | O(n) | O(n2) | O(1) | 稳定 |
快排 | O(nlogn) | O(nlogn) | O(n2) | O(logn) | 不稳定 |
插入排序 | O(n2) | O(n) | O(n2) | O(1) | 稳定 |
希尔排序 | O(n1.3)不确定 | O(n) | O(n2) | O(1) | 不稳定 |
选择排序 | O(n2) | O(n2) | O(n2) | O(1) | 不稳定 |
堆排序 | O(nlogn) | O(nlogn) | O(nlogn) | O(1) | 不稳定 |
归并排序 | O(nlogn) | O(nlogn) | O(nlogn) | O(n) | 稳定 |
基数排序 | O(n*k) | O(n*k) | O(n*k) | O(n+k) | 稳定 |
计数排序 | O(n+k) | O(n+k) | O(n+k) | O(n+k) | 稳定 |
桶排序 | O(n+k) | O(n) | O(n2) | O(n+k) | 稳定 |
稳定性记忆法:一堆(堆排序)作业,心态不稳,快(快排)选择(选择排序)一些(希尔排序)朋友出去玩。
1.冒泡排序
大学刚开始接触三个基本排序(选择、插入、冒泡)中比较好的。n个数排序需要比较n-1趟,每趟需要比较n-i次,比较后会将最大或最小的泡沉底,用两个for循环表示。最好情况是当升序排列时,待排序数列是升序,反之,最坏情况是待排序数列降序,走了完全两个循环。
代码:
void BubSort(vector &a){ //升序
int len=a.size();
for(int i=0;ia[j+1]){ //比较两者,将较大的交换到后边
swap(a[j],a[j+1]);
}
}
}
}
2.选择排序
和名字一样,通过选择最大或最小值进行排序。在未排序数列中选择出最大或最小的放入排序数列起始位置,然后在剩下未排序的数列中继续选择最大或最小值放到排序数列后面。什么时候时间复杂度都是O(n2),数据越小越好,不占额外内存。
代码:
void SelectSort(vector &a){ //升序
int len=a.size();
int min_index,tmp;
for(int i=0;i
3.插入排序
和名字一样,对于未排序数列,在已排序数列中找到正确位置进行插入。向后扫描过程需要将反复将已排序数列向后移动,由for和while表示,当升序排序时,待排序为降序效果最差O(n2),待排序为升序效果最好O(n),反之亦然。
代码:
void insertSort(vector &a){ //升序
int len = a.size();
int preIndex, current;
for (int i = 1; i < len; i++) {
preIndex = i - 1;
current = a[i];
while (preIndex >= 0 && a[preIndex] > current) {
arr[preIndex + 1] = arr[preIndex];
preIndex--;
}
a[preIndex + 1] = current;
}
}
4.希尔排序
又称缩小增量排序,是插入排序的升级版,与插入排序的不同之处在于,它会优先比较距离较远的元素。
希尔排序首先定义一个增量gap=len/2,设len=10时,gap=5,此时意味着原数组分为了五个部分,每部分包括两个元素,接着对这五部分采用直接插入排序,得到第一次结果。然后缩小增量gap=gap/2=2,此时原数组分为了两部分,同样对两部分进行直接插入排序,得到结果。最后以此类推,得到最终结果。
希尔增量的选择与证明是个难题,本例选择的2不是最优。面试一般会比较少问到希尔排序。
代码:
void shellSort(vector &arr) {
int len = arr.size();
for (int gap = Math.floor(len / 2); gap > 0; gap = Math.floor(gap / 2)) {
for (int i = gap; i < len; i++) {
int j = i;
int current = arr[i];
while (j - gap >= 0 && current < arr[j - gap]) {
arr[j] = arr[j - gap];
j = j - gap;
}
arr[j] = current;
}
}
}
5.归并排序
归并排序是基于分治思想的排序算法,通过归并操作实现排序。将原数组划分足够小的子数组(一个),对子数组进行排序,然后将有序的子数组合并成大的有序子数组,以此类推,直到整个数组完全有序。
代码:
void MergeSort(vector &arr,int L,int R)
{
if (L < R)
{
int mid = L + ((R-L)>>2); // (L+R)/2
MergeSort(arr,L,mid);
MergeSort(arr,mid+1,R);
merge(arr,L,mid,R);
}
}
void merge(vector &arr,int L,int mid,int R)
{
vector tmp(R-L+1);
int p1=L,p2=mid+1,i=0;
while(p1<=mid && p2<=R)
{
tmp[i++] = arr[p1]>arr[p2] ? arr[p2++] : arr[p1++];
}
while(p1<=mid)
tmp[i++] = arr[p1++];
while(p2<=R)
tmp[i++] = arr[p2++];
for (int i=0;i= end {
return
}
mid:=(start + end) / 2
mergeSort(arr, start, mid)
mergeSort(arr, mid+1, end)
merge(arr, start, mid, end)
}
func merge(arr []int, start, mid, end int) {
var tmpArr = []int{}
var s1, s2 = start, mid+1
for s1<= mid && s2<= end{
if arr[s1] > arr[s2] {
tmparr = append(tmparr, arr[s2])
s2++
} else {
tmparr = append(tmparr, arr[s1])
s1++
}
}
if s1<=mid {
tmparr = append(tmparr, arr[s1: mid+1]...)
}
if s2<=end {
tmparr = append(tmparr, arr[s2: end+1]...)
}
for pos,item:=range tmparr{
arr[start + pos] = item
}
}
6.快速排序
利用关键字(基准)通过一趟排序将整个数组分为两部分,左边部分小于关键字,右边大于关键字,然后继续对两边利用这种方法排序,采用分治法划分两个部分。
void QuickSort(int array[], int low, int high)
{
int i = low;
int j = high;
if(i > j)
return;
int temp = array[low];
while(i != j)
{
while(array[j] >= temp && i < j)
j--;
if(i
int Partiton(vector &array, int low, int high){
// 三数取中,避免取得最大值或者最小值
int mid = low + (high- low)/2;
if(array[low] > array[high])
swap(array[low], array[high]);
if(array[mid] > array[high])
swap(array[mid], array[high]);
if(array[mid] > array[low])
swap(array[mid], array[low]);
int pivot = array[low];
// 执行交换
while(low < high){
while(low < high && array[high] >= pivot)
high--;
swap(array[low], array[high]);
while(low < high && array[low] <= pivot)
low++;
swap(array[low], array[high]);
}
return low;
}
// 非递归快速排序
void QuickSort(vector &array){
if(array.size() <= 1) return ;
stack st; // 用栈保存每一个待排序子串的首尾元素下标
int mid = Partiton(array, 0, array.size()-1);
if(mid > 1){
st.push(0);
st.push(mid-1);
}
if(mid < array.size()-2){
st.push(mid + 1);
st.push(array.size()-1);
}
while(!st.empty()){
int right = st.top();
st.pop();
int left = st.top();
st.pop();
mid = Partiton(array, left, right);
if(left < mid-1){
st.push(left);
st.push(mid-1);
}
if(right > mid+1){
st.push(mid+1);
st.push(right);
}
}
}
7.堆排序
利用堆的数据结构进行排序,堆是父节点大于或小于子节点的树形结构。
步骤:
代码:
var len; // 因为声明的多个函数都需要数据长度,所以把len设置成为全局变量
function buildMaxHeap(arr) { // 建立大顶堆
len = arr.length;
for (var i = Math.floor(len/2); i >= 0; i--) {
heapify(arr, i);
}
}
function heapify(arr, i) { // 堆调整
var left = 2 * i + 1,
right = 2 * i + 2,
largest = i;
if (left < len && arr[left] > arr[largest]) {
largest = left;
}
if (right < len && arr[right] > arr[largest]) {
largest = right;
}
if (largest != i) {
swap(arr, i, largest);
heapify(arr, largest);
}
}
function swap(arr, i, j) {
var temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
function heapSort(arr) {
buildMaxHeap(arr);
for (var i = arr.length - 1; i > 0; i--) {
swap(arr, 0, i);
len--;
heapify(arr, 0);
}
return arr;
}
根据输入位数,对每一位排序,入桶,每位时间复杂度o(n),总o(n*k),k为最大位数
代码:
func RadixSort(arr []int) []int {
maxNum := getMax(arr)
for exp := 1; maxNum/exp > 0; exp *= 10 {
arr = countSort(arr, exp)
}
return arr
}
func getMax(arr []int) int {
max := math.MinInt32
for _, v := range arr {
if v > max {
max = v
}
}
return max
}
func countSort(arr []int, exp int) []int {
n := len(arr)
output := make([]int, n)
count := make([]int, 10)
for i := 0; i < n; i++ {
index := (arr[i] / exp) % 10
count[index]++
}
for i := 1; i < 10; i++ {
count[i] += count[i-1]
}
for i := n - 1; i >= 0; i-- {
index := (arr[i] / exp) % 10
output[count[index]-1] = arr[i]
count[index]--
}
for i := 0; i < n; i++ {
arr[i] = output[i]
}
return arr
}