常见排序算法总结

常见排序算法总结

这里总结了常见的排序算法,包括冒泡排序、选择排序、直接插入排序、快速排序、归并排序、计数排序、桶排序、堆排序,这里关注的重点实际应该是算法的思想,运用这个思想可以解决很多实际的问题。另外根据思想,能够快速写出算法的实现也是非常有必要的,特别是堆排序、快速排序、归并排序,很多时候要用到其变体,这就要有根据原始思想,快速写出的水平。

#include<iostream>
#include<math.h>

using namespace std;

/**
 *  以从大到小排序为例,手写各个排序算法
 *  输入:待排序的数组
 *  输出:从达到小排好序的数组
 */

void swap(int *a, int *b) {
    int tmp = *a;
    *a = *b;
    *b = tmp;
}

void show(int *a, int n) {
    for (int i = 0; i < n; i++) {
        cout << a[i] << " ";
    }
    cout << endl;
}

/**
 * 选择排序:每次从剩余的数组中选择最大的数放到已排序好的数组后面
 * @return
 */

void selectSort(int *a, int n) {
    for (int i = 0; i < n; i++) {
        int cur = a[i];
        int k = i;
        for (int j = i + 1; j < n; j++) {
            if (a[j] > cur) {
                cur = a[j];
                k = j;
            }
        }
        if (i != k) {
            swap(&a[i], &a[k]);
        }
    }
}

/*
 * 冒泡排序:n轮比较,当前值和下一个值比较,如果比下一个值小,交换;
 * 然后下一个值为当前值,继续往后与下一个值比较,一轮下来,最小的值就冒泡到数组最最末端
 */

void pubbleSort(int *a, int n) {
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < n - i - 1; j++) {
            if (a[j] < a[j + 1]) {
                swap(a[j], a[j + 1]);
            }
        }
    }
}

/**
 * 快速排序:快速排序使用了分治法来加速排序
 * 有一个基准元素,快排将数组小于基准元素的都移动到数组右边,大于基准元素的都移动到数组左边,基准元素最后放中间
 * 然后对大于基准元素部分递归,对小于记住元素部分也进行递归
 */
int quickSortParition(int *a, int i, int j) {
    int tmp = a[i];
    while (i < j) {
        while (i < j && a[j] <= tmp) {
            j--;
        }
        if (i < j) {
            a[i] = a[j];
            i++;
        }
        while (i < j && a[i] >= tmp) {
            i++;
        }
        if (i < j) {
            a[j] = a[i];
            j--;
        }
    }
    a[i] = tmp; // 子数组分离好之后基准元素放到它应该在地方
    return i;
}

void quickSort(int *a, int left, int right) {
    if (left < right) {
        int index = quickSortParition(a, left, right);
        quickSort(a, 0, index - 1);
        quickSort(a, index + 1, right);
    }
}

/**
 * 归并排序:采用分治法思想,对两个有序的子数组进行排序,这里利用了子数组的有序特性
 * 有点类似于树的后序遍历,数组拆分为两部分,一部分视作左子树,一部分视作右子树
 * 现将左边的数组排好序,再讲右边的数组排好序,然后再整体排序
 * @return
 */

void merge(int *a, int left, int mid, int right, int *tmp) {
    int i = left, j = mid + 1, k = left;
    while (i <= mid && j <= right) {
        if (a[i] >= a[j]) {
            tmp[k++] = a[i];
            i++;
        } else {
            tmp[k++] = a[j];
            j++;
        }
    }
    while (i <= mid) {
        tmp[k++] = a[i++];
    }
    while (j <= right) {
        tmp[k++] = a[j++];
    }
    // 将排好序的数组复制到原数组
    k = left;
    while (k <= right) {
        a[k] = tmp[k];
        k++;
    }
}

void mergeSortFun(int *a, int left, int right, int *tmp) {
    if (left < right) {
        int mid = (left + right) / 2;
        mergeSortFun(a, left, mid, tmp);
        mergeSortFun(a, mid + 1, right, tmp);
        merge(a, left, mid, right, tmp);
    }
}

void mergeSort(int *a, int n) {
    int *tmp = new int[n];
    mergeSortFun(a, 0, n - 1, tmp);
}


/**
 * 直接插入排序:将数组看成一个有序数组和一个无序数组
 * 每次从无序数组中选择一个数插入到有序数组的合适位置
 * @return
 */

void insertSort(int *a, int n) {
    for (int i = 1; i < n; i++) {
        int tmp = a[i];
        int j = i - 1;
        for (; j >= 0; j--) {
            if (a[j] < tmp) {
                a[j + 1] = a[j];
            } else { // 已经找到合适的位置了,停止
                break;
            }
        }
        a[j + 1] = tmp;
    }
}


/**
 * 计数排序:这种排序算法可以在线性时间复杂度完成数据排序
 * 当然,根据奥卡姆剃刀原则,计数排序在时间上这么快,那么必然在其他地方很慢,
 * 那就是需要空间比较大,适用的范围比较小
 * @return
 */

int findMax(int *a, int n) {
    int tmp = a[0];
    for (int i = 1; i < n; i++) {
        if (a[i] > tmp) {
            tmp = a[i];
        }
    }
    return tmp;
}

void countSort(int *a, int n) {
    int maxNum = findMax(a, n);
    int *tmp = new int[maxNum + 1];
    for (int i = 0; i < maxNum + 1; i++) {
        tmp[i] = 0;
    }
    for (int i = 0; i < n; i++) {
        tmp[a[i]]++;
    }
    int index = 0;
    for (int i = maxNum; i >= 0; i--) {
        while (tmp[i] > 0) {
            a[index] = i;
            index++;
            tmp[i]--;
        }
    }
}

/**
 * 桶排序:桶排序将数据分到不同的桶里,每个桶里再进行排序
 * 会有一个映射函数,把每个数都映射到对应的桶中,然后对每个桶排序,可以使用快速排序、插入排序
 * 然后顺序读取每个桶的数据得到一个排好序的数组
 * 桶排序非常快,但是耗费的空间也是非常大
 *
 * @return
 */

struct BucketNode {
    BucketNode *next;
    int value;
};

int bucketFun(int value, int intervalLength) {
    return (int) ceil((double) value / intervalLength);
}

// 每个桶内部使用插入排序
void bucketSort(int *a, int n) {
    const int bucketNum = 5;
    BucketNode *bucket = new BucketNode[bucketNum];
    for (int i = 0; i < bucketNum; i++) {
        bucket[i].value = -1;
        bucket[i].next = NULL;
    }
    int amax = a[0];
    int amin = a[0];
    for (int i = 1; i < n; i++) {
        if (a[i] > amax) {
            amax = a[i];
        }
        if (a[i] < amin) {
            amin = a[i];
        }
    }
    int intervalLength = (int) ceil((amax - amin) * 1.0 / bucketNum);
    cout << "max=" << amax << " min" << amin << " bucketNum" << bucketNum << endl;
    for (int i = 0; i < n; i++) {
        int bucketNo = bucketFun(a[i], intervalLength) - 1; // 这里减1因为数组下标是从0开始的
        BucketNode *p = &bucket[bucketNo];
        while (p->next) { // 桶的内部进行直接插入
            auto cur = p->next;
            if (a[i] <= cur->value) { // 这里考虑到稳定性,加了等号
                p = cur; // 因为要求逆序,如果a[3]=5,a[4]=5,
            } else {                 // 第二个5需要排在后面
                BucketNode *q = new BucketNode;
                q->value = a[i];
                q->next = p->next;
                p->next = q;
                break;
            }
        }
        if (p->next == NULL) {  // 如果是在桶的末尾插入
            BucketNode *q = new BucketNode;
            q->value = a[i];
            q->next = p->next;
            p->next = q;
        }
    }
    int k = 0;
    for (int i = bucketNum - 1; i >= 0; i--) {
        BucketNode *p = &bucket[i];
        while (p->next) {
            p = p->next;  // 桶头会跳过去
            a[k++] = p->value;
        }
    }
}

/**
 * 堆排序:堆排序首先需要新建一个堆,然后使用堆调整算法,将堆顶的元素取下来
 * 放到排好序的数组中,调整堆使之变成一个标准堆,再取下堆顶元素,依次进行
 * 调整算法:从最后一个非叶子结点开始,依次往前,首先比较该结点左右孩子的大小
 * 然后取较小者与该结点比较,如果该结点比孩子节点小,两个节点互换
 * @return
 */

// 倒序排序,需要构造小根堆,堆顶元素最小
// {a[0], a[1], ... ,a[n]} 的最后一个非叶子节点编号为 (n-1) / 2
// 因为下标编号是从0开始的,需要减1
// eg a[0]-a[5] 最后一个元素编号为2,a[0]-a[6]的最后一个编号也是2
void adjustHeap(int *a, int i, int n) {
    int cur = a[i];
    int j = i * 2 + 1;
    while (j < n) {
        if (j + 1 < n && a[j + 1] < a[j]) {
            j++;
        }
        if (a[j] < cur) {
            a[(j - 1) / 2] = a[j];
            i = j;
            j = j * 2 + 1;
        } else {
            break;
        }
    }
    a[i] = cur;
}

void buildHeap(int *a, int n) {
    for(int i = (n-1) / 2; i >=0; i--){
        adjustHeap(a, i, n);
    }
    cout<<endl;
    for (int i = n - 1; i >= 0; i--) {
        swap(a[0], a[i]);
        adjustHeap(a, 0, i);
    }
}


int main() {
    int a[10] = {3, 6, 1, 9, 5, 3, 2, 8, 3, 2};
//    selectSort(a, 10);
//    quickSort(a, 0, 9);
//    mergeSort(a, 10);
//    insertSort(a, 10);
//    countSort(a, 10);
//    bucketSort(a, 10);
    buildHeap(a, 10);
    show(a, 10);
    return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值