冒泡排序,快速排序,归并排序,插入排序,希尔排序,堆排序,计数排序,桶排序,基数排序

Posted tobeornottobe

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了冒泡排序,快速排序,归并排序,插入排序,希尔排序,堆排序,计数排序,桶排序,基数排序相关的知识,希望对你有一定的参考价值。

选择排序,冒泡排序,快速排序,归并排序,插入排序,希尔排序,计数排序,桶排序,基数排序
以上是一些常用的排序算法。

选择排序
for(int i = 0; i < n; i++) {
int minval = a[i];
int minid = i;
for (int j = i+1; j < n; j++) {
if (a[j] < minval) {
minid = j;
minval = a[j];
}
}
swap(a[i], a[minid]);
}
最简单的就是选择排序,就是每次遍历数组,然后依次得到第一小的,第二小的,知道整个数组递增有序。所以时间复杂度也很好理解就是O(n^2),

冒泡排序
冒泡排序时学编程开始学到的第一个排序,刚开始还觉得很难理解,哈哈~

用两种思路,每次从后往前,大的下沉;或者从前往后小的上浮;

大的下沉,最次的最大会在右边:

https://blog.csdn.net/u012864854/article/details/79404463

include <bits/stdc++.h>

using namespace std;
int n;
int a[10] = {9, 19, 7, 2, 4, 5, 6, 8, 10, 11};
void showArray() {
for (int i = 0; i < n; i++) {
cout << a[i] << " ";
}
cout << endl;
}
int main() {
// cin >> n;
n = 10;
for (int i = 0; i < n-1; i++) {//最多进行n-1趟
for (int j = 0; j < n-1-i; j++) {
if (a[j] > a[j + 1]) swap(a[j], a[j+1]);
}
}
showArray();
return 0;
}
或者小的上浮:

include <bits/stdc++.h>

using namespace std;
int n;
int a[10] = {9, 19, 7, 2, 4, 5, 6, 8, 10, 11};
void showArray() {
for (int i = 0; i < n; i++) {
cout << a[i] << " ";
}
cout << endl;
}
int main() {
// cin >> n;
n = 10;
for (int i = 0; i < n-1; i++) {//最多进行n-1趟
for (int j = n-1; j > i; j--) {
if (a[j] < a[j - 1]) swap(a[j], a[j - 1]);
}
}
showArray();
return 0;
}
由此可见时间复杂度O(N^2),但是最好的情况下(递增序列),O(N*lgN)

快速排序
一般情况下时间复杂度是O(N*lgN),但是不稳定,最坏也是O(N^2);

快排采用分治,递归的思想,进行排序;

总体的思路就是首先定义l = 0, r = len-1,两个端点,

每次选择当前数组第一个数作为基点,然后在满足l <=r的情况下,不断交换右边和左边的数;然后递归进行;

void quickSort(int a[], int left, int right) {
if (left > right) return;
int base = a[left];

int i = left,  j = right;
while (i < j) {
    while (i < j && a[j] >= base) j--;
    while (i < j && a[i] <= base) i++;
    swap(a[i], a[j]);
}
a[left] = a[i];
a[i] = base;
quickSort(a, left, i-1);
quickSort(a, i+1, right);

}
归并排序
所谓的归并排序,就是每次把两个两个的子序列合并排序,直到整个序列有序,刚开始的子序列长度是1,然后不断*2递增;

时间复杂度O(N*lgN),比较稳定,最好也是O(N)

void mergeSort() {
for (int step = 2; step/2 <= n; step *= 2) {
for (int i = 0; i < n; i += step) {
sort(tempOri + i, tempOri + min(i + step, n));
}
}
}
或者采用分治递归;

void mergearray(int arr[], int from, int mid, int to, int temp[]) {
int i = from;
int inter = mid + 1;
int k = 0;
while (from <= mid && inter <= to) {
if (arr[from] < arr[inter])
temp[k++] = arr[from++];
else
temp[k++] = arr[inter++];
}
while (from <= mid)
temp[k++] = arr[from++];
while (inter <= to)
temp[k++] = arr[inter++];

for (int j = 0; j < k; j++) {
    arr[j + i] = temp[j];
}

}

void mergeSort(int arr[], int from, int to, int temp[]) {
if (from < to) {
int mid = (from + to) / 2;
mergeSort(arr, from, mid, temp);
mergeSort(arr, mid + 1, to, temp);
mergearray(arr, from, mid, to, temp);
}
}

插入排序
每次像已经排好序的序列里插入一个新的数,然后对当前进行排序,所以过程中的每次得到的一个小的序列都是递增有序的;

比较稳定,一般复杂度O(N*lgN),最好O(N)

void insertSort() {
for (int i = 1; i < n; i++) {
int temp = tempOri[i], j = i;
while (j > 0 && tempOri[j - 1] > temp) {
tempOri[j] = tempOri[j - 1];
j--;
}
tempOri[j] = temp;
}
}
希尔排序
希尔排序时插入的排序的升级版,有一个递减的增量序列,一般都是按数组大小n,不断按n/2的增量div,每次比较相隔增量之间这几个数,对这几个数进行排序;

时间复杂度不确定,不稳定,最好就是O(N)。

void shellSort() {
int len = n;
for (int div = len/2; div >= 1; div = div/2) {
//对于每一个增量
for (int i = 0; i <= div; i++) {
for (int j = i; j < len - div; j += div) {
for (int k = j; k < len; k += div) {
if (tempOri[j] > tempOri[k]) {
swap(tempOri[j], tempOri[k]);
}
}
}
}
}
}

堆排序 
堆排序并不是很稳定,然后时间复杂度一般是O(N*lgN);

对于一个大顶堆,堆顶的元素是最大的,所以把它和最后一个元素交换后,最后一个元素就是最大的,然后在对前面的除了已经确定大小顺序的(后面的)进行堆调整。循环如此的操作,依次在倒数第二个得到第二大的数,如此就可以得到递增序列。

因此要求递增的序列,用堆排序需要 用大顶堆,递减的序列,用堆排序需要用小顶堆。

首先是建堆,从最后一个非叶子结点开始往前依次建堆,调整,保证每个结点都是以其为根结点的子树中权值最大的结点。证见算法导论。

//调整大顶堆,在已经建立好的基础下;
void downAjust(int low, int high) {
int i = low, j = i2;//j为左孩子结点
while (j <= high) {//存在孩子结点
//如果存在右孩子,并且右孩子结点的值大于左孩子结点的值
if (j + 1 <= high && heap[j + 1] > heap[j]) {
j++;
}
if (j <= high && heap[j] > heap[i]) {
swap(heap[j], heap[i]);
i = j;
}
else {
break;
}
j = i
2;
}
}

void createHeap() {
for (int i = n/2; i >= 1; i--) {
downAjust(i, n);//建堆
}
}
void heapSort() {
//因为最后要求升序,所以先建大顶堆;先从非叶子结点开始
createHeap();
for (int i = n; i > 1; i--) {
swap(tempOri[i], tempOri[1]);//与堆顶交换
downAjust(1, i-1);//调整堆顶
}
}
计数排序
计数排序的基本思想是对于给定的输入序列中的每一个元素x,确定该序列中值小于x的元素的个数(此处并非比较各元素的大小,而是通过对元素值的计数和计数值的累加来确定)。一旦有了这个信息,就可以将x直接存放到最终的输出序列的正确位置上。

比如我们有三个数组a, b, c;

a是原始数组,待排序数组,数组里元素乱序;

然后c数组作为辅助数组,c[a[i]]记录的是当前之前有多少个元素比a[i]小,这样就可以用c数组来得出最后排序数组b中元素的位置,这样b[c[a[i]]] 就是a[i]在最后排序好之后的位置。以上就是基本的思想。

计数排序比较稳定,时间复杂度大约为O(n+k);

借用辅助数组c,得出排序后的序列b

代码如下:

include <bits/stdc++.h>

using namespace std;
const int maxn = 10000;
int a[maxn], b[maxn], c[maxn];
int n;
int main() {
cin >> n;
for (int i = 0; i < n; i++) {
cin >> a[i];
c[a[i]]++;
}
for (int i = 1; i < k; i++) {
c[i] += c[i - 1];
}
for (int i = n-1; i >= 0; i--) {
b[--c[a[i]]] = a[i];
}
for (int i = 0; i < n; i++) {
cout << b[i] << " ";
}
cout << endl;
return 0;
}
此外还可以对计数排序进行空间上的优化:

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

include <bits/stdc++.h>

using namespace std;
const int maxn = 10000;
const int INF = 0x3f3f3f3f;
int a[maxn], b[maxn], c[maxn];
int n;
int main() {
cin >> n;
int MIN = INF, MAX = -1;

for (int i = 0; i < n; i++) {
    cin >> a[i];
    if (a[i] < MIN) { MIN = a[i]; }
    if (a[i] > MAX) { MAX = a[i]; }
}
for (int i = 0; i < n; i++) {
    c[a[i]-MIN]++;
}
//int k = MAX-MIN+1;k为C数组的大小
int k = MAX-MIN+1
for (int i = 1; i < k; i++) {
    c[i] += c[i - 1];
}
for (int i = n-1; i >= 0; i--) {
    b[--c[a[i]-MIN]] = a[i];
}
for (int i = 0; i < n; i++) {
    cout << b[i] << " ";
}
cout << endl;
return 0;

}

桶排序
桶排序很好理解,比如有一个序列a{3, 2, 9, 7, 5}

那么有我们有标号为0-10的桶c,然后遍历a,记录的是c[a[i]]++;

然后按标号从小到大输出,如果桶中元素不止一个,那么就输出多次;

include <bits/stdc++.h>

using namespace std;
const int maxn = 10000;
const int INF = 0x3f3f3f3f;
int a[maxn], b[maxn], c[maxn];
int n;
int main() {
cin >> n;
int MAX = -1;
for (int i = 0; i < n; i++) {
cin >> a[i];
MAX = max(MAX, a[i]);
}
cout << MAX << endl;
int C = MAX + 1;

int c[C];
memset(c, 0, sizeof(c));
for (int i = 0; i < n; i++) {
    c[a[i]]++;
}
for (int i = 0; i < C; i++) {
    if (c[i] > 0) {
        for (int j = 0; j < c[i]; j++) {
            cout << i << " ";
        }
    }
}
return 0;

}
基数排序
时间复杂度为O (nlog(r)m),简称O(N*K)

基数排序(radix sort)属于“分配式排序”(distribution sort),又称“桶子法”(bucket sort)或bin sort,顾名思义,它是透过键值的部份资讯,将要排序的元素分配至某些“桶”中,藉以达到排序的作用,基数排序法是属于稳定性的排序,其时间复杂度为O (nlog(r)m),其中r为所采取的基数,而m为堆数,在某些时候,基数排序法的效率高于其它的稳定性排序法。--百度百科

有两种实现方法

最高位优先(Most Significant Digit first)法,简称MSD法:先按k1排序分组,同一组中记录,关键码k1相等,再对各组按k2排序分成子组,之后,对后面的关键码继续这样的排序分组,直到按最次位关键码kd对各子组排序后。再将各组连接起来,便得到一个有序序列。

最低位优先(Least Significant Digit first)法,简称LSD法:先从kd开始排序,再对kd-1进行排序,依次重复,直到对k1排序后便得到一个有序序列。

时间效率 [1]  :设待排序列为n个记录,d个关键码,关键码的取值范围为radix,则进行链式基数排序的时间复杂度为O(d(n+radix)),其中,一趟分配时间复杂度为O(n),一趟收集时间复杂度为O(radix),共进行d趟分配和收集。 空间效率:需要2*radix个指向队列的辅助空间,以及用于静态链表的n个指针。

include <bits/stdc++.h>

using namespace std;
const int maxn = 10000;
const int INF = 0x3f3f3f3f;
int a[maxn], b[maxn], c[maxn];
int n;
//求数据的最大位数
int maxbit(int data[], int n) {
int d = 1;
int p = 10;
for (int i = 0; i < n; i++) {
while (data[i] >= p) {
p *= 10;
++d;
}
}
return d;
}
void print(int a[], int n) {
for (int i = 0; i < n; i++) {
cout << a[i] << " ";
}
cout << endl;
}
void radixSort(int a[], int n) {
int d = maxbit(a, n);
int tmp[n];
fill(tmp, tmp+n, 0);
int radix = 1;
//进行d次排序
for (int i = 1; i <= d; i++) {
//每次分配前清空计数器
fill(c, c+10, 0);//注意这里只开了10

    for (int j = 0; j < n; j++) {
        int k = (a[j]/radix)%10;//统计桶
        c[k]++;
    }
    for (int j = 1; j < 10; j++) {
        c[j] += c[j - 1];
    }

    for (int j = n-1; j >= 0; j--) {
        int k = (a[j]/radix)%10;
        tmp[--c[k]] = a[j];
    }
    //收集数据到a数组
    for (int j = 0; j < n; j++) {
        a[j] = tmp[j];
    }
    radix *= 10;
}
print(a, n);

}

int main() {
int n;
cin >> n;
for (int i = 0; i < n; i++) {
cin >> a[i];
}
radixSort(a, n);
return 0;
}

/**
9
5 9 3 12 7 6 4 2 0

**/











































































































































































































































以上是关于冒泡排序,快速排序,归并排序,插入排序,希尔排序,堆排序,计数排序,桶排序,基数排序的主要内容,如果未能解决你的问题,请参考以下文章

八大基础排序中(直接插入排序,希尔排序,冒泡排序, 快速排序,归并排序,简单选择排序)

插入排序(直接插入排序希尔排序);交换排序(冒泡排序快速排序);选择排序(简单选择排序堆排序);归并排序和基数排序;基于关键词比较的排序算法下界分析

基本排序算法(冒泡排序 选择排序 插入排序 快速排序 归并排序 基数排序 希尔排序)

排序算法整理:冒泡排序堆排序插入排序归并操作快速排序希尔排序选择排序

JAVA实现经典排序算法(冒泡排序选择排序插入排序希尔排序堆排序归并排序快速排序)

七种基本排序算法(希尔排序,直接插入排序,冒泡排序,快速排序,简单选择排序,归并排序,堆排序)