排序算法汇总

来源:互联网 发布:b2c商城网站源码下载 编辑:程序博客网 时间:2024/06/08 02:58

排序:

以下排序都是按从小到大的顺序编写的;

1)直接插入排序:
时间复杂度O(n*n)

这里写图片描述

//从小到大//直接插入排序:取出一个比,比完再插入void InsertSort(int* arr, size_t n){    assert(arr);    for(size_t i = 0 ; i < n-1; i++)    {        int temp = arr[i+1];        int j = i;        for(; j >= 0; --j)        {            if(arr[j] > temp)            {                arr[j+1] = arr[j];            }            else                break;        }        arr[j+1] = temp;     }}

2)希尔排序:预排序+插入排序
类似于插入排序的,只不过是跨步比(+gap),是插入排序的一种优化;
预排序:尽可能的将大数放后面,小数放前面,不是完全有序的,当gap越小越接近于有序,当gap==1时就是插入排序,当gap>1是希尔排序;

//希尔排序//时间复杂度:O(n)< O <O(n*n)void shellSort(int* arr,size_t n){    assert(arr);    int gap = 3;    while(gap > 1)    {        gap = gap/3 + 1;        for(size_t i = 0; i < n-gap; i++)        {            int temp = arr[i+gap];            int j = i;            for(; j >= 0; j -= gap)            {                if(arr[j] > temp)                {                    arr[j+gap] = arr[j];                }                else                    break;            }            arr[j+gap] = temp;        }    }}

3)选择排序:
选择排序是最坏的排序;
时间复杂度是:O(n*n)

//选择排序----最坏的排序,这里我们一次性选出最大最小值void selectSort(int* arr,size_t n){    size_t begin = 0;    size_t end = n-1;    while(begin < end)    {        int min = begin;        int max = begin;        for(size_t i = begin+1; i <= end; i++)        {            if(arr[i] < arr[min])            {                min = i;            }            if(arr[i] > arr[max])            {                max = i;            }        }        swap(arr[min],arr[begin]);        //如果此时最大值就是begin下标的那个值,        //经过上一步的交换,最大值被换到了下标是min的位置上去了        if(begin == max)        {            max = min;        }        swap(arr[max],arr[end]);        ++begin,--end;    }}

4)堆排序:分为大堆和小堆
时间复杂度是:O(n*lgn)
//建大堆,先把最大的值放在上面,然后再让最大值和最后一个值交换,然后再进行向下调整,再把第二大放在最上面,依次类推;

//从小到大//堆排序----建大堆void AdjustDown(int* arr,size_t n,int father){    int childL = 2*father+1;    while(childL < n)    {        if((childL+1) < n && arr[childL+1] > arr[childL])        {            childL++;        }        if(arr[father] < arr[childL])        {            swap(arr[father],arr[childL]);            father = childL;            childL = 2*father+1;        }        else            break;    }}void HeapSort(int* arr,size_t n){    assert(arr);    for(int i = (n-2)/2; i >= 0; --i)    {        AdjustDown(arr,n,i);    }    //目前最大值在顶层    int end = n-1;    while(end > 0)    {        swap(arr[0],arr[end]);        //调整        AdjustDown(arr,end,0);        --end;    }}

5)冒泡排序;
这应该是我们最熟悉的排序吧,不做特别说明直接见代码:

//冒泡排序void BubbleSort(int* arr,size_t n){    assert(arr);    for(size_t j = 0; j < n-1; j++)    {        //单趟        int heap = 0;        for(size_t i = 0; i < n-1; i++)        {            if(arr[i] > arr[i+1])            {                heap = 1;                swap(arr[i],arr[i+1]);            }        }        if(heap == 0)        {            break;        }       }}

6)快速排序:—-递归过程

A.左右指针法:
左边找大于key的,右边找小于key的
key就是最右边那个值(实际值);
begin和end是下标

这里写图片描述

//左右指针法int PartSort(int* arr,int begin,int end){    int right = end;    int key = arr[end];    while(begin < end)    {        //先判断左指针        while(arr[begin] < key && begin < end)        {            ++begin;        }        //再判断右指针        while(arr[end] >= key && begin < end)        {            --end;        }        //交换两个指针找到的值        swap(arr[begin],arr[end]);    }    swap(arr[begin],arr[right]);    return begin;}

B.挖坑法:(和左右指针法类似,只不过这次是将选中的那个数挖走交换)

这里写图片描述
这里写图片描述

//挖坑法int PartWSort(int* arr,int begin,int end){    int newkey = GetMidKey(arr,begin,end);    /*int pit = end;*/    swap(arr[newkey],arr[end]);    int key = arr[end];    while(begin < end)    {        while(arr[begin] < key && begin < end)        {            ++begin;        }        /*if(begin < end)        {            arr[pit] = arr[begin];            pit = begin;        }*/        arr[end] = arr[begin];        while(arr[end] >= key && begin < end)        {            --end;        }        /*if(begin < end)        {            arr[pit] = arr[end];            pit = end;        }*/        arr[begin] = arr[end];    }    /*arr[pit] = key;*/    arr[end] = key;    return begin;}

C.前后指针法:
一前一后两个指针:
这里写图片描述

cur走到9的时候,9>key,++cur(多走一步),所以++prev走到9的位置,prev != cur,所以交换5和9:

这里写图片描述

//前后指针法int PartPBSort(int* arr,int begin,int end){    assert(arr);    int indexKey = GetMidKey(arr,begin,end);//返回的是下标    /*int key = arr[end];*/    swap(arr[indexKey],arr[end]);    int key = arr[end];    int cur = begin;    int prev = cur - 1;    while(cur < end)    {        if(arr[cur] < key && ++prev != cur)        {            swap(arr[prev],arr[cur]);        }        ++cur;    }    swap(arr[end],arr[++prev]);    return prev;}void QuickSort(int* arr,int begin,int end){    assert(arr);    if(begin < end)    {        int div = PartWSort(arr,begin,end);    /*  int div = PartPBSort(arr,begin,end);*/        QuickSort(arr,begin,div-1);        QuickSort(arr,div+1,end);    }}

但是快速排序也有缺陷:
Eg:2 3 230 32 2 3 3 3 32 2 2 3 3 233
以上就无法用快排,降低了快速排序的性能;如果每次选中的数据是最大/最小值,那么就是N*N的复杂度;
所以要避免每次选取key的时候选择最大、最小值特有以下解决方法:
① 三数取中法:每次都给出最左边和最右边的值(下标),然后求中间值(下标),再比较三个数大小(实际值),找出中间值(实际值)的作为key,返回的是下标;

//三数取中法---取键值int GetMidKey(int* arr,int begin,int end){    assert(arr);    int mid = begin + ((end - begin) >> 1);    if(arr[begin] < arr[mid])    {        if(arr[begin] > arr[end])        {            return begin;        }        else if(arr[mid] < arr[end])        {            return mid;        }        else        {            return end;        }    }    else    {        //arr[begin] >= arr[mid]        if(arr[mid] > arr[end])        {            return mid;        }        else if(arr[begin] > arr[end])        {            return end;        }        else        {            return begin;        }    }}

② 小区间优化快排法:其实就是当你将要排序的小区间的长度小于某个固定值(这里我们设为10)时,采用插入排序法,如果不是就还是采用快排的方式排序;

//小区间优化法快排void MinizoneQuickSort(int* arr,int begin,int end){    assert(arr);    int size = end - begin;    if(size <= 0)    {        return;    }    else if(size < 10)    {        InsertSort(arr+begin,size+1);    }    else    {        int div = PartSort(arr,begin,end);        MinizoneQuickSort(arr,begin,div-1);        MinizoneQuickSort(arr,div+1,end);    }}

③ 非递归快排;(利用stack的结构)
其实思想和快排的递归思想一致,只不过我们需要创建一个栈stack,来模拟实现递归过程的压栈过程,我们压下标即可;

//非递归快排void QuickSortNonR(int* arr,int begin,int end){    assert(arr);    stack<int> _stack;    if(begin < end)    {        _stack.push(end);//放进去的是下标        _stack.push(begin);    }    while(!_stack.empty())    {        int left = _stack.top();        _stack.pop();        int right = _stack.top();        _stack.pop();        int div = PartSort(arr,left,right);        if(div+1 < right)        {            _stack.push(right);            _stack.push(div+1);        }        if(left < div-1)        {            _stack.push(div-1);            _stack.push(left);        }    }}

7)归并排序:
思想:递归切分,先将begin和end区间逐个切分到一个区间就一个数,然后开始回退比较,这样生成的左右两边有序小区间排序,生成一个大一点的有序小区间后,继续向上回退,再左右两边排序,这样一直回退到顶端;
这里写图片描述
这种思想适合大量数据排序,时间复杂度是N*lgN,空间复杂度为O(N);这是一种外排序——–>磁盘上排序(如果内存放不下就放在磁盘上)

//归并排序----外排序----磁盘void _Sort(int* arr,int* temp,int begin1,int end1,int begin2,int end2){    int start = begin1;    int finish = end2;    int index = begin1;    while(begin1 <= end1 && begin2 <= end2)    {        if(arr[begin1] > arr[begin2])        {            temp[index++] = arr[begin2++];        }        else        {            temp[index++] = arr[begin1++];        }    }    while(begin1 <= end1)    {        temp[index++] = arr[begin1++];    }    while(begin2 <= end2)    {        temp[index++] = arr[begin2++];    }    memcpy(arr+start,temp+start,sizeof(int)*(finish - start + 1));}//分成小区间void _MergeSort(int* arr,int* temp,int begin,int end){    if(begin >= end)    {        return;    }    int mid = begin + ((end - begin) >> 1);    _MergeSort(arr,temp,begin,mid);    _MergeSort(arr,temp,mid+1,end);    _Sort(arr,temp,begin,mid,mid+1,end);}void MergeSort(int* arr,int n){    assert(arr);    int* temp = new int[n];//先创建一个临时变量,并开辟空间    _MergeSort(arr,temp,0,n-1);    delete [] temp;//自己申请的空间用完以后要自己释放}

8)并查集:只针对下标(缺陷)
将N个不同的元素分成一组不相交的集合;
开始时每个元素都是一个集合,然按照规律将两个集合进行合并
也就是说会将与集合中某些元素相关的多个小集合合并成大集合,比如说朋友圈,{1,3},{2,3},{3,4},{5,6}——->{1,2,3,4},{5,6}这样的,我们同通过如下方法分配他们之间的关系:
分析过程如下:
这里写图片描述
这里写图片描述
这里写图片描述

//并查集class UnionFindSet{protected:    vector<int> _ufs;public:    UnionFindSet(size_t n)    {        _ufs.resize(n,-1);    }    //注:每次放入的一组数据一定是一个集合;    void SetUnion(int x1,int x2)    {        int root1 = _FindRoot(x1);        int root2 = _FindRoot(x2);        if(root1 != root2)        {            if(_ufs[root1] == -1)            {                swap(root1,root2);            }            _ufs[root1] += _ufs[root2];            _ufs[root2] = root1;        }    }    //判断是否是一个集合    bool IsOneUnion(int x1,int x2)    {        return _FindRoot(x1) == _FindRoot(x2);    }    //判断有几个根    int GetRootCount()    {        size_t count = 0;        for(size_t i = 0; i < _ufs.size(); i++)        {            if(_ufs[i] < 0)            {                ++count;            }        }        return count;    }private:    int _FindRoot(int x)    {        int root = x;        if(_ufs[x] >= 0)        {            root = _ufs[x];        }        return root;    }};int friends(int n,int m,int r[][2]){    UnionFindSet ufs(n+1);    for(size_t i = 0; i < m; i++)    {        ufs.SetUnion(r[i][0],r[i][1]);        cout<<ufs.IsOneUnion(r[i][0],r[i][1])<<" ";    }    return ufs.GetRootCount()-1;//去掉_ufs[0]}void TestUnion(){    int n = 6;    const int m = 4;    int r[m][2] = {{1,3},{2,3},{3,4},{5,6}};    cout<<friends(n,m,r)<<endl;}