十大经典排序算法(超详细)

本文详细介绍了十大经典排序算法,包括冒泡排序、选择排序、堆排序、插入排序、希尔排序、快速排序、归并排序、基数排序、计数排序和桶排序,以及它们的核心思想、时间空间复杂度分析,帮助读者理解排序算法在编程中的应用和优化策略。
摘要由CSDN通过智能技术生成

排序是我们数据结构中不可或缺的算法,也是面试官HR最爱问的问题之一,所以要清楚排序就要明白排序的核心思想是什么,什么样的排序时间和空间复杂度更低,什么情况下用什么样的排序会更优。

最常见的就是十大经典排序:冒泡排序、选择排序、堆排序、插入排序、希尔排序、快速排序、归并排序、基数排序、计数排序和桶排序。

冒泡排序

算法思想

  1. 比较相邻的元素。如果第一个比第二个大,就交换他们两个。
  2. 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。
  3. 针对所有的元素重复以上的步骤,除了最后一个。
  4. 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。

void BubbleSort(int* a, int n) {
    for (int i = 0; i < n - 1; i++) {
        bool exchange = false;
        for (int j = 1; j < n - i; j++) {
            if (a[j-1] > a[j]) {
                int temp = a[j];
                a[j] = a[j -1];
                a[j - 1] = temp;
                exchange = true;
            }
        }
        if (exchange == false) {
            break;
        }
    }
}

选择排序

算法思想

  1. 在未排序序列中找到最小(大)元素,存放到排序序列的起始位置
  2. 从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾
  3. 以此类推,直到所有元素均排序完毕

void Swap(int* p1, int* p2) {
    int tmp = *p1;
    *p1 = *p2;
    *p2 = tmp;
}
void SelectSort(int* a, int n) {
    int begin = 0, end = n - 1;
    while (begin < end) {
        int maxi = begin, mini = begin;
        for (int i = begin; i <= end; i++) {
            if (a[i] > a[maxi]) {
                maxi = i;
            }
            if (a[i] < a[mini]) {
                mini = i;
            }
        }
        Swap(&a[begin], &a[mini]);
        //如果maxi和begin重叠,修正一下即可
        if (begin == maxi) {
            maxi = mini;
        }
        Swap(&a[end], &a[maxi]);
        ++begin;
        --end;
    }
}

堆排序

算法思想

  1. 将初始待排序关键字序列(R1,R2….Rn)构建成大顶堆,此堆为初始的无序区;
  2. 将堆顶元素R[1]与最后一个元素R[n]交换,此时得到新的无序区(R1,R2,……Rn-1)和新的有序区(Rn),且满足R[1,2…n-1]<=R[n];
  3. 由于交换后新的堆顶R[1]可能违反堆的性质,因此需要对当前无序区(R1,R2,……Rn-1)调整为新堆,然后再次将R[1]与无序区最后一个元素交换,得到新的无序区(R1,R2….Rn-2)和新的有序区(Rn-1,Rn)。不断重复此过程直到有序区的元素个数为n-1,则整个排序过程完成。

void Adjustdown(int*a,int n,int parent) {
    int child = parent * 2 + 1;
    while (child < n) {
        //找出小的那个孩子
        if (a[child + 1] < n && a[child + 1] > a[child]) {
            child++;
        }
        if (a[child] > a[parent]) {
            Swap(&a[child], &a[parent]);
            parent = child;
            child = parent * 2 + 1;
        }
        else {
            break;
        }
    }
}
//排升序
void HeapSort(int* a, int n) {
    //建大堆
    for (int i = (n - 1 - 1) / 2; i >= 0; i--) {
        Adjustdown(a,n,i);
    }
    int end = n - 1;
    while (end > 0) {
        Swap(&a[0], &a[end]);
        Adjustdown(a, end, 0);
        --end;
    }
}

插入排序

算法思想

  1. 从第一个元素开始,该元素可以认为已经被排序
  2. 取出下一个元素,在已经排序的元素序列中从后向前扫描
  3. 如果该元素(已排序)大于新元素,将该元素移到下一位置
  4. 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置
  5. 将新元素插入到该位置后
  6. 重复步骤2~5

void InsertSort(int* a, int n) {
    for (int i = 0; i < n-1; i++) {
        //[0,end]有序,插入tmp依旧有序
        int end = i;
        int tmp = a[i+1];
        while (end >= 0) {
            if (a[end] > tmp) {
                a[end + 1] = a[end];
                --end;
            }
            else {
                break;
            }
        }
        a[end+1] = tmp;
    }
}

希尔排序

算法思想

  1. 选择一个增量序列t1,t2,…,tk,其中ti>tj,tk=1;
  2. 按增量序列个数k,对序列进行k 趟排序;
  3. 每趟排序,根据对应的增量ti,将待排序列分割成若干长度为m 的子序列,分别对各子表进行直接插入排序。仅增量因子为1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。

//希尔排序:1.预排序--接近有序 2.插入排序
void ShellSort(int* a, int n) {

    int gap = n;     //归纳公式:合计gap组,每组n/gap个
    while (gap > 1) {
        //gap = gap / 3+1;
        gap = gap / 2;
        for (int i = 0; i < n - gap; i++) {
            int end = i;
            int tmp = a[end + gap];
            while (end >= 0) {
                if (a[end] > tmp) {
                    a[end + gap] = a[end];
                    end -= gap;
                }
                else {
                    break;
                }
            }
            a[end + gap] = tmp;
        }
    }
}

快速排序

算法思想

  1. 选取第一个数为基准
  2. 将比基准小的数交换到前面,比基准大的数交换到后面
  3. 对左右区间重复第二步,直到各区间只有一个数

//三数取中(针对有序)
int GetMidIndex(int* a, int left, int right) {
    int mid = (left + right) / 2;
    //int mid = left + (rand() % (right - left)); //针对全是一样的
    if (a[left] < a[mid]) {
        if (a[mid] < a[right]) {//a[left] < a[mid] < a[right]
            return mid;
        }
        else if (a[right] < a[left]) {// a[right] < a[left] < a[mid]
            return left;
        }
        else {
            return right;
        }
    }
    else {// a[left] > a[mid]
        if (a[mid] >a[right]) {//a[left] > a[mid] > a[right]
            return mid;
        }
        else if (a[right] > a[left]) {//a[right] > a[left] > a[mid]
            return left;
        }
        else {
            return right;
        }
    }
}
//hoare(左右指针法)
//[left,right]
// L、R相遇:R先走,R停下来的位置小于等于key;L先走,L停下来的位置大于等于key
int PartSort(int* a, int left, int right) {
    int midi = GetMidIndex(a, left, right);
    Swap(&a[left], &a[midi]);
    int keyi = left;//左边做key,右边先走,保障了相遇位置的值小于等于key
  //int keyi=right; //右边做key,左边先走,保障了相遇位置的值大于等于key
    while (left < right) {
        //右边找小
        while (left < right && a[right] >= a[keyi])
            right--;
        //左边找大
        while (left < right && a[left] <= a[keyi])
            left++;
        Swap(&a[left], &a[right]);
    }
    Swap(&a[left], &a[keyi]);
    return left;
}
//挖坑法
int PartSort2(int* a, int left, int right) {
    int midi = GetMidIndex(a, left, right);
    Swap(&a[left], &a[midi]);
    int key = a[left];
    int hole = left;
    while (left < right) {
        //右边找小
        while (left < right && a[right] >= key)
            right--;
        a[hole] = a[right];
        hole = right;
        //左边找大
        while (left < right && a[left] <= key)
            left++;
        a[hole] = a[left];
        hole = left;
    }
    a[hole] = key;
    return hole;
}
//前后指针法
//1.最开始prev和cur相邻
//2.当cur遇到比key大的值以后,他们之间的值都是比key大的值
//3.cur找小,找到小的以后跟++prev位置的值交换,相当于大翻滚式的往右推同时把小的换到左边
int PartSort3(int* a, int left, int right) {
    int midi = GetMidIndex(a, left, right);
    Swap(&a[left], &a[midi]);
    int prev = left;
    int keyi = left;
    int cur = left + 1;
    while (cur <= right) {
        if (a[cur] < a[keyi] && ++prev != cur) {
            //++prev;
            Swap(&a[prev], &a[cur]);
        }
        ++cur;
    }
    Swap(&a[prev], &a[keyi]);
    keyi = prev;
    return keyi;
}

void QuickSort(int* a, int begin, int end) {
    if (begin>=end) {
        return;
    }
    //int keyi = PartSort(a, begin, end);
    //int keyi = PartSort2(a, begin, end);
    int keyi = PartSort3(a, begin, end);//分割区间
    // [begin,kryi-1] keyi [keyi+1,end]
    QuickSort(a, begin, keyi - 1);
    QuickSort(a, keyi+1, end);
}

归并排序

算法思想

1.把长度为n的输入序列分成两个长度为n/2的子序列;

2. 对这两个子序列分别采用归并排序;

3. 将两个排序好的子序列合并成一个最终的排序序列。

void _MergeSort(int* a, int begin, int end,int* tmp) {
    if (begin == end)
        return;
    //小区间优化
    if (end - begin + 1 < 10) {
        InsertSort(a+begin, end - begin + 1);
        return;
    }
    int mid = (begin + end) / 2;
    //[begin,mid][mid+1,end]
    _MergeSort(a, begin, mid, tmp);
    _MergeSort(a, mid+1, end, tmp);
    //归并两个区间
    int begin1 = begin, end1 = mid;
    int begin2 = mid+1, end2 = end;
    int i = begin;
    while (begin1 <= end1 && begin2 <= end2) {
        if (a[begin1] <= a[begin2]) {
            tmp[i++] = a[begin1++];
        }
        else {
            tmp[i++] = a[begin2++];
        }
    }
    while (begin1 <= end1) {
        tmp[i++] = a[begin1++];
    }
    while (begin2 <= end2) {
        tmp[i++] = a[begin2++];
    }
    memcpy(a+begin, tmp+ begin, sizeof(int) * (end - begin + 1));
}
void MergeSort(int* a, int n) {
    int* tmp = (int*)malloc(sizeof(int) * n);
    if (tmp == NULL) {
        perror("malloc fail\n");
        return;
    }
    _MergeSort(a, 0, n - 1,tmp);
    free(tmp);
}

基数排序

算法思想

  1. 取得数组中的最大数,并取得位数;
  2. arr为原始数组,从最低位开始取每个位组成radix数组;
  3. 对radix进行计数排序(利用计数排序适用于小范围数的特点)

int maxbit(int data[], int n) //辅助函数,求数据的最大位数 {

int maxData = data[0];

///< 最大数 /// 先求出最大数,再求其位数,这样有原先依次每个数判断其位数,稍微优化点。

for (int i = 1; i < n; ++i) {

if (maxData < data[i])

maxData = data[i];

}

int d = 1;

int p = 10;

while (maxData >= p) {

//p *= 10;

// Maybe overflow maxData /= 10; ++d;

}

return d;

void radixsort(int data[], int n) //基数排序 {

int d = maxbit(data, n);

int *tmp = new int[n];

int *count = new int[10]; //计数器

int i, j, k;

int radix = 1;

for(i = 1; i <= d; i++) //进行d次排序 {

for(j = 0; j < 10; j++)

count[j] = 0; //每次分配前清空计数器

for(j = 0; j < n; j++) {

k = (data[j] / radix) % 10; //统计每个桶中的记录数

count[k]++;

}

for(j = 1; j < 10; j++)

count[j] = count[j - 1] + count[j]; //将tmp中的位置依次分配给每个桶

for(j = n - 1; j >= 0; j--) //将所有桶中记录依次收集到tmp中 {

k = (data[j] / radix) % 10;

tmp[count[k] - 1] = data[j]; count[k]--;

}

for(j = 0; j < n; j++) //将临时数组的内容复制到data中

data[j] = tmp[j];

radix = radix * 10;

}

delete []tmp;

delete []count;

}

计数排序

算法思想

  1. 找出待排序的数组中最大和最小的元素;
  2. 统计数组中每个值为 i 的元素出现的次数,存入数组 C 的第 i 项;
  3. 对所有的计数累加(从 C 中的第一个元素开始,每一项和前一项相加);
  4. 向填充目标数组:将每个元素 i 放在新数组的第 C[i] 项,每放一个元素就将 C[i] 减去 1;

void CountSort(int* a, int n) {
    int min = a[0], max = a[0];
    for (int i = 0; i < n; i++) {
        if (a[i] < min) {
            min = a[i];
        }
        if (a[i] > max) {
            max = a[i];
        }
    }
    int range = max - min + 1;
    int* countA = (int*)malloc(sizeof(int) * range);
    memset(countA, 0, sizeof(int) * range);
    //统计次数
    for (int i = 0; i < n; i++) {
        countA[a[i] - min]++;
    }
    //排序
    int k = 0;
    for (int j = 0; j < range; j++) {
        while (countA[j]--) {
            a[k++] = j + min;
        }
    }
}

桶排序

算法思想

  1. 设置一个定量的数组当作空桶子。
  2. 寻访序列,并且把项目一个一个放到对应的桶子去。
  3. 对每个不是空的桶子进行排序。
  4. 从不是空的桶子里把项目再放回原来的序列中。

function bucketSort(arr, bucketSize) {

if (arr.length === 0) {

return arr;

}

var i;

var minValue = arr[0];

var maxValue = arr[0];

for (i = 1; i < arr.length; i++) {

if (arr[i] < minValue) {

minValue = arr[i]; // 输入数据的最小值

}

else if (arr[i] > maxValue) {

maxValue = arr[i]; // 输入数据的最大值

}

}

// 桶的初始化

var DEFAULT_BUCKET_SIZE = 5; // 设置桶的默认数量为5

bucketSize = bucketSize || DEFAULT_BUCKET_SIZE;

var bucketCount = Math.floor((maxValue - minValue) / bucketSize) + 1;

var buckets = new Array(bucketCount);

for (i = 0; i < buckets.length; i++) {

buckets[i] = [];

}

// 利用映射函数将数据分配到各个桶中

for (i = 0; i < arr.length; i++) {

buckets[Math.floor((arr[i] - minValue) / bucketSize)].push(arr[i]);

}

arr.length = 0;

for (i = 0; i < buckets.length; i++) {

insertionSort(buckets[i]); // 对每个桶进行排序,这里使用了插入排序

for (var j = 0; j < buckets[i].length; j++) {

arr.push(buckets[i][j]);

}

}

return arr;

}

十大经典排序算法之所以经典,自然有它的道理。如果觉得这篇文章对你有帮助可以收藏下来,也欢迎大家进行批评指正,理解排序算法可以帮助我们更加高效的编写程序,一起加油

  • 14
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值