排序算法

1. 直接选择排序

任何情况下的时间复杂度都是 O(n^2), 最差的一种排序,每次都必须遍历玩整个子数组,选出最小的那个,空间复杂度为O(1),无需额外空间。直接选择排序的关键在于,每次从右侧子数组中,选出最小的,放到子数组第一个,然后缩小子数组范围,再次选择、交换。
#include <iostream>
using namespace std;

void selectionSort(int* a, int begin, int end)
{
    if (begin >= end) return; 
    int min = a[begin]; 
    for (int i = begin; i < end; i++)
    {
        if (a[i] < min)
        {
            // 不使用第三个数交换两个整型。
            a[i] = a[i] ^ a[begin]; 
            a[begin] = a[i] ^ a[begin]; 
            a[i] = a[i] ^ a[begin];
            min = a[begin];
        }
    }
    selectionSort(a, ++begin, end);
}

int main()
{
    int n[7] = { 10, 10, 20, 1, 3, 8, 22 };
    selectionSort(n, 0, 7);

}

2. 直接插入排序

直接插入排序的平均复杂度为O(n^2),在最好的情况下,即基本有序时,复杂度为O(n).直接插入排序的关键在于,与直接选择相反,直接插入的操作序列是左侧的已排好序列,每次把右侧第一个值拿出来插到左侧已排好序列中。
    int* insertionSort(int* A, int n) {
        for(int i = 1; i < n; i++)
            for(int j = i; j > 0; j--)
                if(A[j] < A[j - 1]) swap(A[j], A[j - 1]);
        return A;
    }

3. 希尔排序

希尔排序是直接插入排序的改良版,gap不再是1,而是动态变化,从n/2开始,每次除以2,直到1。利用了插入排序在基本有序的前提下效率最高的特点。
void shell_sort(int a[], int n)
{
    for (int gap = n / 2; gap > 0; gap /= 2) // gap 从 n / 2 .. 1
    {
        // 下面是插入排序
        for (int i = gap; i < n; i++)   // 遍历一遍
        {// 步长不再是直接插入时的1,而是gap
         // 前gap个([0] .. [gap - 1])是不需要检测的,因为是每组的第一个,所以i从gap开始
            for (int j = i; j >= gap; j -= gap) // 注意这里 j-=gap
            {
                if (a[j] < a[j - gap]) swap(a[j - gap], a[j]);
                else break; 
            }
        }
    }
}

4. 直接交换(冒泡排序)

冒泡排序的平均时间复杂度为O(n^2),在最好情况下,即基本有序时,复杂度为O(n)。冒泡排序的关键是,每次冒泡上来一个当前子序列中的最大值(升序排列的话)。冒泡排序可以优化,比如下面,用exchange标记,如果某次冒泡没有交换,说明当时的子序列已经有序。

#include <iostream>
using namespace std;

void exchangeSort(int* n, int s)
{
    // 优化冒泡排序
    bool isexchange = false; 
    for (int i = 0; i < s; i++)
    {
        isexchange = false;
        for(int j = 0; j < s - i - 1; j++)
        {
            if (n[j + 1] < n[j])
            {
                isexchange = true; 
                n[j + 1] ^= n[j]; 
                n[j] ^= n[j + 1];
                n[j + 1] ^= n[j];
            }
        }
        if (isexchange == false) return;
    }
}

int main()
{
    int n[7] = { 10, 10, 20, 1, 3, 8, 22 };
    exchangeSort(n, sizeof(n)/ sizeof(int)); 
}

5.快速排序(加强版交换排序)

关键字:pivot 轴,partition() (划分)
快排有两种实现,分别应用于数组和单向链表,快排的关键是partition函数的实现 http://www.cnblogs.com/TenosDoIt/p/3665038.html
1. 定轴实现
对数组进行快排时使用这个方法。
快排采用了 分治策略,平均时间复杂度为O(nlogn)。快排的关键在于选择基准,把子序列从左右两端,(如果是升序的话)左边找比基准大的,右边找比基准小的,交换两者,直到左右指针相遇。一个while嵌套两个查找while。快排在最坏情况下(逆序),复杂度为O(n^2)
partition 函数:
// 划分函数,返回pivot的最终位置
int partition(int a[], int begin, int end)
{
    // 选第一个作pivot即可
    int pivot = a[begin];
    int l = begin, r = end; 
    while (l < r)
    {
        // 从右往左找小的
        while (l < r && a[r] >= pivot) --r;
        a[l] = a[r];// 找到后把它放到低位,a[l]上肯定要么是a[begin]要么是大于pivot的,所以可以覆盖(提示:考虑下面这个循环退出的条件)
        while (l < r && a[l] <= pivot) ++l;
        a[r] = a[l];// 找到后把它放到高位,a[r]上肯定是小于pivot的,可以覆盖
    }
    // 此时 l 应该等于 r,这个是一个空位,可以覆盖,把pivot放到这里,左边的小于pivot,右边的大于pivot
    a[l] = pivot;
    return l;
}
快排的另一种实现是挖坑实现
2. 挖坑法
对链表进行快排时使用这个方法
// 划分函数,返回pivot的最终位置
int partition(int a[], int begin, int end)
{
    int pivot = a[begin]; //选第一个作pivot
    int low = begin; // 比pivot小的最后一个元素的下标
    for (int i = begin + 1; i <= end; i++) // 比pivot小的放在左边
        if (a[i] < pivot)
            swap(a[i], a[++low]);//将此数与比pivot小的最后一个元素的下一个(比pivot大)交换
    swap(a[begin], a[low]); // 将最后一个比pivot小的元素交换到头部
    return low;
}
两种实现的 quicksort入口都是一样的,
void quicksort(int a[], int begin, int end)
{
    if (begin < end)
    {
        int pivot = partition(a, begin, end); // 使得总体有序,找到pivot的最终位置,
        quicksort(a, begin, pivot - 1); // 使得左侧有序
        quicksort(a, pivot + 1, end); // 使得右侧有序
    }
}

6. 归并排序

关键字:merge,() mergesort()
归并排序有两个函数,一个是用来合并两个已经排好的数组,并放到临时数组里,比较容易
// 将 begin .. end 排好序后放回a数组的 begin .. end 原位置
// a[]的begin .. mid 与 mid + 1 .. end 子数组分别是两个已经排好序的数组
// 需要借用一个辅助存储空间 temp, 这也是归并排序空间复杂度为O(n)的原因所在
void merge(int a[], int begin, int mid, int end, int temp[])
{
    int l = begin, r = mid + 1; 
    int k = 0; 
    while (l <= mid && r <= end) // 当两个子数组都没用完时
        temp[k++] = (a[l] < a[r] ? a[l++] : a[r++]);
    while (l <= mid) // 当第一个没有用完时,用完它
        temp[k++] = a[l++]; 
    while (r <= end) // 当第二个没有用完时,用完它
        temp[k++] = a[r++];
    // 将temp中的内容拷贝回 a[]的begin, end位置
    for (int i = begin; i <= end; i++)
        a[i] = temp[i - begin]; 
}
然后是主调函数,是一个拆分函数
// 主调函数
void mergesort(int a[], int begin, int end, int temp[])
{
    if (begin < end)
    {
        int mid = (begin + end) >> 1; // 取中点
        mergesort(a, begin, mid, temp); // 另左边有序,临时数组temp可以多次使用
        mergesort(a, mid + 1, end, temp); // 另右边有序
        merge(a, begin, mid, end, temp); // 合并左右两个有序子数组
    }
}
用的时候,传进来 数组名,0, len - 1, 和一个临时数组即可

快排与归并排序的对比

1. 快排和归并排序都应用了分治的思想。快排是交换排序的一种。
2. 快排最坏复杂度为 O(n^2), 与pivot选取有关,会退化为冒泡排序,归并排序最坏也是O(n^2)
3. 归并排序需要另外的O(n)存储空间;快排不需要,直接对原数组操作。
4. 快排每次保证整体有序,即pivot左侧一定都小于pivot,pivot右侧一定都大于pivot,然后分别处理左侧和右侧,使之整体有序,直到只剩一个元素;
归并排序每次保证局部有序,划分到最底部,然后从低开始向上merge合并,从而使整体有序。
5. 归并排序稳定,快排不稳定。

7. 堆排序

堆排序是一种选择排序,关键是下调算法。堆排序的第一步是将近似堆(原数组)转化为最大堆(从最后一个非叶节点开始调用下调算法),然后每次删除根(将根与最后一个节点交换),再使用下调,调整为新的堆,再删除根。。。直到子堆大小为1。
// 下调算法
// begin 代表了子堆根,从此开始下调
// end 代表了子堆最后一个节点
void heap_adjust(int a[], int begin, int end)
{
    for (int i = begin * 2 + 1; i <= end; i = i * 2 + 1)
    {
        int next = i + 1;
        if (next <= end && a[next] > a[i]) i++; // 如果自己的兄弟比自己大,则应该让兄弟和爸爸交换
        if (a[begin] > a[i]) break; // 已经满足了堆次序性 
        swap(a[begin], a[i]);
        begin = i; // 以此节点为根继续下调
    }
}
// 先得把无序数组建立为一个大根堆(升序的话)
// 使用下调建堆(从最后一个非叶节点开始,重复利用下调),这样就可以
// 把建堆和调堆两个过程用一个函数 heap_adjust 进行
void heap_sort(int a[], int n)
{
    // 先用建堆算法,建立最大堆,从最后一个非叶节点开始调用下调算法
    for (int i = n - 1; i >= 0; i--)
        heap_adjust(a, i, n - 1); 
    // 每次把堆根和最后一个元素交换,然后再用下调算法调堆
    for (int i = 0; i < n; i++)
    {
        swap(a[n - i - 1], a[0]); 
        heap_adjust(a, 0, n - i - 2);
    }
}

8. 桶排序

桶排序非常快,但是空间消耗大。
8.1 计数排序 counting sort
基本思路是用一个数组记录每个值出现的次数,数组的下标才是值,数组的值是次数,输出的时候从0开始到max,有几个输出几个。
#include <iostream>
#include <algorithm>
using namespace std;

void countingsort(int a[], int n)
{
    if (n == 0) return;
    // 1. 先找最大值
    int max = a[0]; 
    for (int i = 0; i < n; i++)
        max = (max < a[i] ? a[i]: max);
    // 2. 做桶
    int * bucket = new int[max + 1];
    memset(bucket, 0, sizeof(int)*(max + 1));
    for (int i = 0; i < n; i++)
        bucket[a[i]] ++; 
    int k = 0;
    for (int i = 0; i < max + 1; i++)
    {
        int n = bucket[i]; 
        while (n-- > 0)
            a[k++] = i;
    }
    delete[] bucket; 
}

int main()
{
    int a[] = { 1,7,3,5,2,4,6 };
    const int len = sizeof(a) / sizeof(int);
    countingsort(a, len);
    for (int i = 0; i < len; i++)
        cout << a[i] << endl;
}

8.2 基数排序是从各位开始装桶。


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值