数据结构之排序算法总结

基本概念:

排序的稳定性:待排序的文件中,若存在多个关键字相同的记录,经过排序后这些记录的之间的相对次序不变。即是稳定的。其中,希尔排序,快速排序,堆排序,简单选择排序是不稳定的,其它都是稳定排序。

1 冒泡排序(改进版)
时间复杂度o(n^2),最好o(n)。稳定的。空间复杂度1.

void BubbleSort(vector<int>& data)
{
    bool flag = true;     //当排序已完成,没必要再继续扫描
    int length = data.size();
    for (int i=0;i<length && flag;++i)
    {
        for (int j = length -2;j>=i;--j)
        {
            flag = false;
            if (data[j + 1] < data[j])
            {
                int temp = data[j + 1];
                data[j + 1] = data[j];
                data[j] = temp;
                flag = true;
            }
        }
    }
}

2 简单选择排序
时间复杂度o(n^2),空间复杂度o(1),不稳定的。
(大白话版)依次找最小值与假设的最小值交换

void SelectSort(vector<int>& data)
{
    int i,j,min;
    for(i=0;i<data.size();++i)
    {
        min = i;
        for(j=i+1;j<data.size();++j)
        {
            if(data[j]<data[min])
                min = j;
        }
        if(min!=i)
        {
            int temp = data[i];
            data[i] = data[min];
            data[min] = temp;
        }
    }
}

3 直接插入排序
空间复杂度1,时间复杂度o(n^2),最好o(n)。稳定的。
http://www.cnblogs.com/XavierJian/p/5775321.html 有动图,方便理解思想

void InsertSort(vector<int>& data)
{
    int j;
    for(int i=1;i<data.size();++i)                //假设data[0]完成排序
    {
        if(data[i]<data[i-1])
        {
            int key = data[i];
            for(j=i-1; j>=0 && data[j]>key;j--)   //大于key的值依次后移
                data[j+1] = data[j];
            data[j+1] = key;                      //key赋给空位
        }
    }
}

4 希尔排序
时间复杂度,o(n^1.5),空间复杂度1.不稳定的。
(大白话版)进化版的直接插入排序,假设书架上有一排书,每隔一个增量贴上相同颜色的贴纸,再对颜色相同的分组进行插入排序,最后增量为1时,排序完成。此时待排序序列已经相对有序,所以要快。

void ShellSort(vector<int>& data)
{
    int i,j,key;
    int gap = data.size();
    while(gap>1)
    {
        gap = gap/3 +1;                       //增量序列
        for(i=gap;i<data.size();++i)
        {
            if(data[i]<data[i-gap])           //每一个子序列进行直接插入排序
            {
                key = data[i];
                for(j=i-gap;j>=0 && data[j]>key;j -= gap)
                    data[j+gap] = data[j];
                data[j+gap] = key;          
            }       
        }
    }
}

5 堆排序
时间复杂度o(nlogn), 空间复杂度1,不稳定的。

void HeapAjust(vector<int>& data, int start, int end)
{
    int temp = data[start];
    for(int j=2*start+1;j<=end;j=j*2+1)      //如果当前结点序号为j,其左孩的序号一定为2*j+1,右孩的序号2*j+2 假设序列从0开始
    {
        if(j<end && data[j]<data[j+1])       //左孩与右孩比较
            j++;
        if(temp>data[j])                     //上面二者的最大值与根结点比较
            break;
        data[start] = data[j];               //交换最大值
        start = j;                           //考虑当前结点的最大值(只需要考虑最大值的)的下一层 再找最大值
    }
    data[start] = temp;                      //完成交换操作
}

void HeapSort(vector<int>& data)
{
    int j;
    for(j=data.size()/2-1;j>=0;j--)      //从下往上,从左往右构建最大堆。一个长度为data.size()大小的数组,只需要调整4次,因为只有4个根节点
        HeapAdjust(data, j, data.size()-1);           //将第j位和往下的二叉树调整为最大堆。4次循环结束,最大堆构建完成

    for(j=data.size()-1;j>0;j--)
    {
        int temp = data[0];
        data[0] = data[j];
        data[j] = temp;

        HeapAdjust(data, 0, j-1);
    }
}

6 归并排序
时间复杂度o(nlogn), 空间复杂度o(n)

void merge(vector<int>& data, int low, int mid, int high, vector<int>& out)   //将data两部分合并到out
{
    int i,j,k;
    for(k=low,i=mid+1;low<=mid && i<=high;k++)
    {
        if(data[low]<data[i])
            out[k] = data[low++];
        else
            out[k] = data[i++];
    }
    if(low<=mid)
    {
        for(j=0;j<=mid-low;++j)
            out[k+j] = data[low+j];
    }
    if(i<=high)
    {
        for(j=0;j<=high-i;++j)
            out[k+j] = data[i+j];
    }
}

void MergeSort(vector<int>& data, vector<int>& out, int start, int end)
{
    vector<int> temp(data.size());
    if(start==end)
        out[start] = data[start];
    else
    {
        int mid = (start+end)/2;
        MergeSort(data, temp, start, mid);
        MergeSort(data, temp, mid+1, end);
        merge(temp, out, start, mid, end);
    }
}

关于归并排序的惊人发现,做剑指offer时发现有道题的归并排序不太一样,没有用到临时temp;也同样能完成排序操作,且效率更高。当有20000个数所用时间比没有temp的要快一倍;
代码如下:
void mergesort(vector<int>& data, vector<int>& out, int start, int end)   //输入的out为data的拷贝
{
    if (start == end)
        out[start] = data[start];
    else
    {
        int mid = (start + end) / 2;
        mergesort(out, data, start, mid);
        mergesort(out, data, mid + 1, end);
        merge(data, out, start, mid, end);
        return (left + right + all);
    }
}

7 快速排序
时间复杂度o(nlogn), 最差o(n^2)。空间复杂度o(logn);不稳定的。

void Swap(vector<int>& data, int i, int j)
{
    int temp = data[i];
    data[i] = data[j];
    data[j] = temp;
}

int partition(vector<int>& data, int low, int high)
{
    int key = data[low];
    while (low<high)
    {
        while (low < high && data[high] >= key)
            high--;
        Swap(data, low, high);
        while (low < high && data[low] <= key)
            low++;
        Swap(data, low, high);
    }
    return low;
}

void QuickSort(vector<int>& data, int low, int high)
{   
    if (low < high)
    {
        int mid = partition(data, low, high);
        QuickSort(data, low, mid - 1);
        QuickSort(data, mid + 1, high);
    }   
}

排序题(剑指offer)

1 数组中出现次数超过一半的数字
数组中一个数字的长度超过数组长度的一半,请找出这个数字。
题目需要注意的点:想到利用partition函数,即小于当前位置的数小于等于当前位置的数,大于当前位置的数大于等于当前位置的数,并且快排是修改了数组本身的,要询问是否可以修改数组本身;判断非法输入,包括指针为空,长度小于1,题目中要求大于长度一半的数不存在。解题不是万事大吉,要考虑非法输入。

bool isValid = false;

void swap(int* data, int i, int j)
{
    int temp = data[i];
    data[i] = data[j];
    data[j] = temp;
}

int partition(int* data, int low, int high)
{
    int key = data[low];
    while (low < high)
    {
        while (low < high && data[high] >= key)
            high--;
        swap(data, low, high);
        while (low < high && data[low] <= key)
            low++;
        swap(data, low, high);
    }
    return low;
}

int GetMidNum(int* data, int length)
{
    isValid = true;
    //判断输入是否有效
    if (!data || length<1)
    {
        isValid = false;
        return -1;
    }
    //核心代码找到中位数
    int start = 0;
    int end = length-1;
    int middle = length >> 1;
    int pos = partition(data, start, end);
    while (pos != middle)
    {
        if (pos < middle)
            pos = partition(data, pos+1, end);
        else        
            pos = partition(data, start, pos-1);
    }
    //判断超过一半的数是否存在
    int cnt = 0;
    for (int i = 0; i < length; ++i)
    {
        if (data[i] == data[pos])
            cnt++;
    }
    if (cnt <= middle)
    {
        isValid = false;
        return -1;
    }

    return data[pos];
}

2 最小的k个数
输入n个整数,找出其中最小的k个数

vector<int> GetFirstK(int* data, int length, int k)
{
    vector<int> v;
    //判断非法输入
    if (!data || length<1 || k>length)
        return v;
    //核心代码找到定位k
    int start = 0;
    int end = length - 1;
    int pos = partition(data, start, end);
    while (pos != k-1)
    {
        if (pos < k-1)
            pos = partition(data, pos + 1, end);
        else
            pos = partition(data, start, pos - 1);
    }
    for (int i = 0; i < k; ++i)
        v.push_back(data[i]);
    return v;
}

3 数组中的逆序对
数组中的两个数字如果前面一个数字大于后面的数字,则这两个数字构成一个逆序对。输入一个数组,求出这个数组逆序对的总数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值