排序基础--(插入排序、交换排序、选择排序、归并排序)

插入排序
一、直接插入排序
思路:选择一个有序区间,然后依次拿出后面的数往有序区间里面插入
第一步:第一个数即为一个有序区间
第二步:取出无序区间的第一个数,从有序区间最后一个数开始依次向前比较,判断大小
第三步:为有序区间的数挪出合适的位置并放入,直到无序区间数据取完,整个序列排序完成

code:

//这里以升序为例:
#include <iostream>
using namespace std;
#include <assert.h>

//升序
void Insertsort(int *a,int n)
{
    assert(a);
    for(int i=1; i<n; i++)
    {
        int end = i-1;//有序区间最后一个元素的下标
        int tmp = a[i];
        while(end>=0)
        { 
        if(a[end]>tmp)
        {
            a[end+1] = a[end];//为tmp挪出合适的位置
            --end;
        }
        else{
            break;
        }
    }
        a[end+1] = tmp;
    }
}

void TestInsertSort()
{
    int a[] = {5,4,3,2,1};
    int len = sizeof(a)/sizeof(a[0]);
    Insertsort(a,len);
    for(int i=0; i<len; i++)
    {
    cout<<a[i]<<" ";        
    }
    cout<<endl;
}

int main()
{
    TestInsertSort();
    return 0;
}

为了代码的可复用性,我们可以写两个仿函数来完成升序和降序的工作
code:

#include <iostream>
using namespace std;
#include <assert.h>

//升序
template <class T>
struct Greate
{
    bool operator()(const T& a1,const T& a2)
    {
        return a1 > a2;
    }
};
//降序
template <class T>
struct Less
{
    bool operator()(const T& a1,const T& a2)
    {
        return a1  < a2;
    }
};

//升序
template <class T,class Compare>
void Insertsort(T *a,T n)
{
    assert(a);
    for(int i=1; i<n; i++)
    {
        int end = i-1;//有序区间最后一个元素的下标
        int tmp = a[i];
        while(end>=0)
        { 
        if(Compare()(a[end],tmp))
        {
            a[end+1] = a[end];//为tmp挪出合适的位置
            --end;
        }
        else{
            break;
        }
    }
        a[end+1] = tmp;
    }
}

void TestInsertSort()
{
    int a[] = {5,4,3,2,1};
    int len = sizeof(a)/sizeof(a[0]);
    Insertsort<int,Greate<int>>(a,len);
    for(int i=0; i<len; i++)
    {
    cout<<a[i]<<" ";        
    }
    cout<<endl;
}

int main()
{
    TestInsertSort();
    return 0;
}

时间复杂度空间复杂度分析:
最坏情况:开始的序列是与要排的序列相反的,比如你要拍降序,但是他本来就是升序,那么要进行比较n(n-1)/2,所以时间复杂度为O(n^2)

消耗的空间位常数个,所以时间复杂度为O(1)

适用于数据量很小的序列,比如小于一千。
二、希尔排序
希尔排序是对插入排序的一种优化。在序列相反的情况下,先对序列进行预排序,使序列接近有序,再进行插入排序。

两点:
1、插入排序对于几乎已经排好序的序列操作时,效率很高,能达到线性排序的效率
2、插入排序一次只能讲数据移动一位,效率是很低的。

对此我们做出优化为希尔排序
思路:先将整序列分成若干个子序列(由相隔的某个增量完成),分别对子序列进行插入排序,待整个序列基本有序,再对整体进行插入排序。

code:

//希尔排序
//升序
template <class T>
struct Greate
{
    bool operator()(const T& a1,const T& a2)
    {
        return a1 > a2;
    }
};
//降序
template <class T>
struct Less
{
    bool operator()(const T& a1,const T& a2)
    {
        return a1  < a2;
    }
};

template <class T,class Compare>
void ShellSort(T* a,int n)
{
    assert(a);
    int gap = n; //gap为所给的增量
    while(gap > 1)
    {
        gap = gap/3 +1; //实验证明gap = n/3比较优,加1是为了最后对整体排序
        for(int i=gap; i<n; i+=gap)
        {
            int end = i - gap;
            int tmp = a[i];
            while(end >=0)
            {
            if(Compare()(a[end],tmp))
            {
                a[end + gap] = a[end];
                end -= gap;
            }
            else
            {
                break;
            }
        }
        a[end+ gap] = tmp;

        }
    }
}

void TestShellSort()
{
    int a[] = {1,2,3,4,5,6};
    int len = sizeof(a)/sizeof(a[0]);
    ShellSort<int,Greate<int>>(a,len);
    for(int i = 0; i<len; i++)
    {
        cout<<a[i]<<" ";
    }
    cout<<endl;
}

int main()
{
    //TestInsertSort();
    TestShellSort();
    return 0;
}

时间复杂度&空间复杂度分析

时间复杂度:
因为是插入排序的优化: 大约在O(N^1.25~1.6N^1.25)

空间复杂度为O(1)

交换排序
1、冒泡排序
思路:
对一个序列的数据进行两两比较,比如升序,从开始对数据进行比较,如果前者大于后者进行交换,重复此工作一直到最后两个数交换,第一趟完成,然后缩小排序区间,从头开始第二趟冒泡比较,重复比较直至序列有序

冒泡排序的优化,对已经有序的区间不需要再重复比较,设置标志位,详情见代码:
code:

//冒泡排序
void Bubble(int* a,int n)
{
    int i =0,j=0;
    int flag=0;//设置标志位控制循环次数
    for(i=0;i<n-1;i++)//因为要交换最后一个数和倒数第一个数所以只到n-1
    {
        flag = 0;
        for(j=0;j<n-1;j++)//控制比较的次数
        {
            if(a[j] > a[j+1]) //升序
            {
                int tmp = a[j];
                a[j] = a[j+1];
                a[j+1] = tmp;
                flag = 1;
            }
        }
        if(flag == 0)
        {
            break;
        }
    }
}

void TestBubble()
{
    int a[] = {6,5,4,3,2,1};
    int n = sizeof(a)/sizeof(a[0]);
    Bubble(a,n);
    for(int i=0; i<n;i++)
    {
        cout<<a[i]<<" ";
    }
    cout<<endl;
}

int main()
{
    //TestInsertSort();
    //TestShellSort();
    TestBubble();
    return 0;
}

时间复杂度&空间复杂度
时间复杂度:一个n长序列需要冒泡n-1趟,递减每趟需要冒泡n-1-i(i为递增的),时间复杂度为O(N^2)
空间复杂度:O(1)

场景:冒泡排序是一种很稳定的排序

2、快速排序
思路:是对冒泡排序的一种优化,在序列中先选出一个基准值,比基准值大的放基准值右边,比基准值小的放基准值左边,然后同样的方法递归去划分左区间和右区间,直到某区间只剩下一个数据时停止递归,此区间已经有序,然后向上层区间返回,当最开始的基准值的左右区间已经有序则整个序列排序完成

code:

#include <iostream>
using namespace std;
#include <stack>
#include <assert.h>

int GetMidIndex(int *a, int left, int right)
{
    int mid = left + ((right - left) >> 1);

    if (a[left] < a[mid])
    {
        if (a[mid] < a[right])
        {
            return mid;
        }
        else if (a[left] < a[right])
        {
            return right;
        }
        else
        {
            return left;
        }
    }
    else//a[mid] < a[left]
    {
        if (a[mid] > a[right])
        {
            return mid;
        }
        else if (a[right] < a[left])
        {
            return right;
        }
        else
            return left;
    }
}

//左右指针法
int PartSort1(int* a, int left, int right)
{
    int mid = GetMidIndex(a, left, right);
    swap(a[mid], a[right]);
    int key = right;

    while (left < right)
    {
        while (left < right &&a[left] <= a[key])
        {
            ++left;
        }

        while (left < right && a[right] >= a[key])
        {
            --right;
        }

        //交换左右指针所指的值
        if (a[left] != a[right])
        {
            swap(a[left], a[right]);
        }
    }

    //将key值放到正确位置上
    swap(a[left], a[key]);
    return left;
}

//挖坑法
int ParSort2(int* a, int left, int right)
{
    int mid = GetMidIndex(a, left, right);
    swap(a[mid], a[right]);
    int key = a[right];
    int blank = right;

    while (left < right)
    {
        while (left < right && a[left] <= key)
        {
            ++left;
        }
        a[blank] = a[left];
        blank = left;

        while (left < right && a[right] >= key)
        {
            --right;
        }
        a[blank] = a[right];
        blank = right;
    }
    a[blank] = key;
    return blank;
}

//前后指针法
int PartSort3(int* a, int left, int right)
{
    int mid = GetMidIndex(a, left, right);
    swap(a[mid], a[right]);

    int key = right;
    int cur = left;
    int prev = cur - 1;

    while (cur != right)
    {
        if (a[cur] < a[key] && a[++prev] != a[cur])
        {
            swap(a[prev], a[cur]);
        }
        ++cur;
    }
    swap(a[++prev], a[cur]);
    return prev;
}

//三数取中法


//元素小于13个进行直接插入排序进行优化
void  InsertSort(int* a, size_t n)
{
    int end = 0;
    for (size_t i = 1; i < n; ++i)
    {
        int tmp = a[i];
        int end = i - 1;
        while (end >= 0)
        {
            if (a[end]>tmp)
            {
                a[end+1] = a[end] ;
            }
            else
            {
                break;
            }
        }
        a[end + 1] = tmp;
    }
}

void QuickSortNoR(int* a, int left, int right)
{
    assert(a);
    stack<int> s;
    s.push(right);
    s.push(left);

    while (!s.empty())
    {
        int start = s.top();
        s.pop();
        int end = s.top();
        s.pop();

        //前后指针法实取到div
        int div = PartSort3(a, start, end);

        if (start < div - 1)
        {
            s.push(div - 1);
            s.push(start);
        }
        if (end>div + 1)
        {
            s.push(end);
            s.push(div + 1);
        }
    }
}

//递归实现
void  QuickSort(int* a, int left, int right)
{
    assert(a);
    if (left < right)
    {
        int kiv = PartSort1(a, left, right);

        if (right - left < 2)
        {
            InsertSort((a + left), (right - left + 1));
        }
        else
        {
            QuickSort(a, left, kiv-1);
            QuickSort(a, kiv + 1, right);
        }
    }
}

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

void TestQuickSort()
{
    int a[] = { 2, 0, 4, 9, 6, 5, 3, 1, 7, 8 };

    size_t sz = sizeof(a) / sizeof(a[0]);
    QuickSort(a, 0, sz - 1);
    Print(a, sz);
}

/*快速排序的平均时间复杂度为O(N*logN)
*最坏情况是当分组重复生成一个空序列的时候变成O(N*N)
*优化避免最坏情况:三数取中法->每次选择基准数选择面值居中的元素
*小区间优化:当需要排序的元素小于13个时进行插入排序
*/

时间复杂度&空间复杂度
时间复杂度:快速排序是一种分治法,优化后的时间复杂度为O(N*logN)
空间复杂度为 O(N)

场景:快速排序是一种不稳定的排序,但是它是已知的最快的排序算法

选择排序
一、选择排序
思路:选一个数据放到合适的位置,缩小需要排序的区间,比如选择区间中最小的数据放到第一个位置,缩小区间继续选择次小的放到第二个位置…….直到区间有序

code:

//选择排序
void SelectSort(int*a ,size_t n)
{
    assert(a);
    int left = 0;//未排序区间的左下标
    int right = n-1;//未排序区间的右下标

    while(left < right)
    {
        int minIndex = left;//未排序区间最小数据的位置下标
        int maxIndex = right;//未排序区间最大数据的位置小标

        //选出最大和最小数据的下标
        for(int i = left; i<=right;++i)
        {
            if(a[i] < a[minIndex])
            {
                minIndex = i;
            }
            if(a[i] > a[maxIndex])
            {
                maxIndex = i;
            }
        }

        //修正:最大值在最小值或最小值在最大位置
        swap(a[maxIndex],a[right]);
        if(minIndex == right)
        {
            minIndex = maxIndex;
        }
        swap(a[minIndex],a[left]);

        left++;
        right--;

    }
}

void TestSelectSort()  
{  
    int a[] = {9,5,4,2,3,6,8,7,1,0};  
    size_t sz = sizeof(a)/sizeof(a[0]);  

    //SelectSort1(a,sz);  
    //SelectSort2(a,sz);  
   SelectSort(a,sz); 
   for(int i=0; i<sz; i++)
   {
    cout<<a[i]<<" ";
   }
   cout<<endl;
}

int main()
{
    //TestInsertSort();
    //TestShellSort();
    //TestBubble();
    //testFastSort();
    TestSelectSort();
    return 0;
}

时间复杂度&空间复杂度:
时间复杂度:最坏为O(N^2)
空间复杂度为O(1)

2、堆排序
思路:堆排序是利用了堆这种数据结构的特性产生的一种算法,堆结构近似于一棵完全平衡二叉树。
首先对一个序列进行建立大顶堆,即数的父节点总是大于左右子节点的结构,建立好大顶堆之后,用堆顶和最后一个数据进行交换,此时形成了一个新的无序序列和一个只有一个元素的有序序列,对无序序列在进行调整形成大顶堆,再用堆顶和无序序列的最后一个元素交换,又形成了新的无序序列和有两个元素的有序序列,依次进行调整,交换,直至无序序列剩下一个元素为止。
图解:
这里写图片描述

code:

//堆排序
//堆调整:将堆的末端子节点做调整,使子节点永远小于父节点
void AdjustDown(int* a,int n,int pos)
{
    int  parent = pos;//父节点的下标
    int child = parent*2 + 1;//左孩子的下标
    while(child<n)
    {
        //选左孩子中较大的
        if((child+1 <n) && a[child] < a[child+1])
        {
            ++child;
        }
        if(a[child] > a[parent])
        {
            swap(a[child],a[parent]);

            //向下更新父节点和孩子节点
            parent = child;
            child = parent*2 +1;
        }
        else
        {
            break;
        }
    }
}

//默认升序,建立大堆
void HeaSort(int* a,int n)
{
    assert(a);
    //从最后一个非叶子节点开始向下调整
    for(int i = (n-2)>>1; i>=0;--i)
    {
        AdjustDown(a,n,i);//创建最大堆,将堆所有数据重新排序
    }

    //缩小范围
    for(int i=n-1; i>0; --i)
    {
        swap(a[0],a[i]);//将根节点和最后一个数据交换
        //在进行建大堆
        AdjustDown(a,i,0);
    }
}

void TestHeapSort()
{
    int a[] = {5,4,3,2,1};
    int len = sizeof(a)/sizeof(a[0]);
    HeaSort(a,len);
    for(int i=0; i<len; i++)
    {
        cout<<a[i]<<" ";
    }
    cout<<endl;
}

int main()
{
    //TestInsertSort();
    //TestShellSort();
    //TestBubble();
    //testFastSort();
    //TestSelectSort();
    TestHeapSort();
    return 0;
}

升序,建大堆
降序,建小堆

时间复杂度&空间复杂度
时间复杂度:因为堆近似一颗平衡二树,不断进行交换,向下调整建堆,时间复杂度为O(N*logN)
空间复杂度:因为占据有限个空间,空间复杂度为O(1)

归并排序
思路:归并排序是建立在归并操作上的一种排序算法,采用分治法的思想。
1>按照类似快速排序的方法递归地将待排序序列依次划分为两个区间,区间只剩一个数停止划分;
2>如果一个区间只剩一个数,我们可将其看做有序区间,然后对左右两个小区间进行归并,归并后仍要保持区间的有序性;
3>同2>提到的方法我们每次将两个有序的子区间归并为一个大的有序区间,并返回给上一层递归;
4>直到所有划分的区间归并为一个有序序列,归并排序就算完成。

code:

void Merge(int* a,int* tmp,int left,int mid,int right)
{
    int begin1 = left; //左区间的左边界
    int end1 = mid;    //左区间的右边界
    int begin2 = mid + 1; // 有区间的左边界
    int end2 = right;  //右区间的右边界

    int index = left; //中间数组的下标
    while(begin1<=end1 && begin2 <=end2)
    {
        //将左右区间较小的值放入中间数组
        if(a[begin1] < a[begin2])
        {
            tmp[index++] = a[begin1++];
        }
        else{
            tmp[index++] = a[begin2++];
        }
    }
    //如果左区间还有数据,直接放到中间数据的后边
    while(begin1<=end1)
    {
        tmp[index++] = a[begin1++];
    }
    //如果有区间还有数据,直接放到中间数组的后面
    while(begin2 <=end2)
    {
        tmp[index++] = a[begin2++];
    }

    //将中间数组拷贝回原数组
    memcpy(a,tmp,index*sizeof(int));


}
void _MerSort(int* a,int* tmp,int left,int right)
{
    //当区间只有一个数或没有数据时递归返回
    if(left >= right)
    {
        return;
    }
    int mid = left + ((right-left)>>1);//划分左右区间的中间值
    _MerSort(a,tmp,left,mid);
    _MerSort(a,tmp,mid+1,right);

    //对左右两个有序区间进行归并
    Merge(a,tmp,left,mid,right);
}

void MergeSort(int* a,int n)
{
    assert(a);
    int* tmp = new int[n];
    _MerSort(a,tmp,0,n-1);
    delete[] tmp;
}

void testMergesort()
{
    int a[] = {6,5,4,3,2,1};
    int len = sizeof(a)/sizeof(a[0]);
    MergeSort(a,len);
    for(int i =0; i<len;i++)
    {
        cout<<a[i]<<" ";
    }
    cout<<endl;
}
int main()
{
    //TestInsertSort();
    //TestShellSort();
    //TestBubble();
    //testFastSort();
    //TestSelectSort();
    //TestHeapSort();
    testMergesort();
    return 0;
}

归并排序非递归的code

void Merge_sort(int *a, int length)  
{    
    int i, begin1, end1, begin2, end2, index;    
    int *tmp = new int[length];    

    for (i = 1; i < length; i *= 2)   
    {  
        for (begin1 = 0; begin1 < length - i; begin1 = end2)  
        {     
            begin2 = end1 = begin1 + i;    
            end2 = end1 + i;    

            if (end2 > length)    
                end2 = length;    

            index = 0;    
            while (begin1 < end1 && begin2 < end2)    
                tmp[index++] = a[begin1] > a[begin2] ? a[begin2++] : a[begin1++];    

            while (begin1 < end1)    
                a[--begin2] = a[--end1];    

            while (index > 0)    
                a[--begin2] = tmp[--index];    
        }    
    }  
    delete []tmp;  
}    

时间复杂度&空间复杂度

时间复杂度:采用分而治之的思想,时间复杂度为O(N*logN)
空间复杂度:开辟新空间,空间复杂度为O(N)

这里写图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值