排序算法汇总

排序:

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

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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值