常见排序算法

目录

(1)插入排序

(2)希尔排序

(3)选择排序

(4)堆排序

(5)冒泡排序

(6)快速排序

(7)归并排序

(8)计数排序

(9)桶排序

(10)基数排序


(1)插入排序

        插入排序,一般也被称为直接插入排序。对于少量元素的排序,它是一个有效的算法  。插入排序是一种最简单的排序方法,它的基本思想是将一个记录插入到已经排好序的有序表中,从而一个新的、记录数增1的有序表。在其实现过程使用双层循环,外层循环对除了第一个元素之外的所有元素,内层循环对当前元素前面有序表进行待插入位置查找,并进行移动 。

平均时间复杂度:O(n^2)

最坏时间复杂度:O(n^2)

最好时间复杂度:O(n)

空间复杂度:O(1)

稳定性:稳定

void insertionSort(vector<int>& a)
{
    int len = a.size();
    for (int i = 0, j, temp; i < len - 1; i++) //需要循环次数
    {
        j = i;
        temp = a[i + 1];
        while (j >= 0 && a[j] > temp)
        {
            a[j + 1] = a[j];
            j--;
        }
        a[j + 1] = temp;
    }
}

(2)希尔排序

        希尔排序(Shell's Sort)是插入排序的一种又称“缩小增量排序”(Diminishing Increment Sort),是直接插入排序算法的一种更高效的改进版本。希尔排序是非稳定排序算法。该方法因 D.L.Shell 于 1959 年提出而得名。

        希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至 1 时,整个文件恰被分成一组,算法便终止。

平均时间复杂度:O(n^{1.3})

最坏时间复杂度:O(n^2)

最好时间复杂度:O(n)

空间复杂度:O(1)

稳定性:不稳定 

void ShellSort(vector<int>vec)
{
    // 希尔排序
    for(int gap=vec.size()/2;gap>0;gap/=2)
    {
        // 直接插入排序
        for(int i=gap;i<vec.size();++i)
        {
            int j=i;
            while(j-gap>=0 && vec[j-gap]>vec[j])
            {
                swap(vec[j-gap],vec[j]);
                j=j-gap;
            }
        }
    }
}

希尔排序快不快主要取决于我们怎么取增量序列。

原始希尔排序的取法就是:D_M=N/2,D_k=\frac{D_{k+1}}{2}
此增量序列也成为Shell增量序列
原始希尔排序最坏的时间复杂度为O(n^2)

增量元素不互质,则小增量可能根本不起作用。

Hibbard增量序列

Hibbard增量序列的取法为D_k=2^k-1:{1, 3, 7, 15, 31, 63, 127, 255, 511, 1023, 2047, 4095, 8191...}
最坏时间复杂度为O(n^{\frac{3}{2}});平均时间复杂度约为O(n^{\frac{5}{4}})

Sedgewick增量序列

Sedgewick增量序列的取法为D=9*4^i-9*2^i+14^i-3*2^i+1:{1, 5, 19, 41, 109, 209, 505, 929, 2161...}
最坏时间复杂度为O(n^{\frac{4}{3}});平均时间复杂度约为O(n^{\frac{7}{6}})

(3)选择排序

        选择排序(Selection sort)是一种简单直观的排序算法。它的工作原理是:第一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,然后再从剩余的未排序元素中寻找到最小(大)元素,然后放到已排序的序列的末尾。以此类推,直到全部待排序的数据元素的个数为零。选择排序是不稳定的排序方法。不稳定:因为未排序序列的第一个元素会与最大或者最小元素相交换。

平均时间复杂度:O(n^2)

最坏时间复杂度:O(n^2)

最好时间复杂度:O(n^2)

空间复杂度:O(1)

稳定性:不稳定 

void selectionSort(vector<int> &a)
{
    int len = a.size();
    for (int i = 0, minIndex; i < len - 1; i++) //需要循环次数
    {
        minIndex = i;                     //最小下标
        for (int j = i + 1; j < len; j++) //访问未排序的元素
        {
            if (a[j] < a[minIndex])
                minIndex = j; //找到最小的元素的下标
        }
        swap(a[i], a[minIndex]);
    }
}

(4)堆排序

堆排序(英语:Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。 

建堆的时间复杂度为O(nlogn)从堆顶开始建堆,或者O(n)从堆底开始建堆。

以下代码为小顶堆,x的左子堆为x*2,右子堆为x*2+1

平均时间复杂度:O(n log_2n)

最坏时间复杂度:O(n log_2n)

最好时间复杂度:O(n log_2n)

空间复杂度:O(1)

稳定性:不稳定 

struct heap
{
    int n;
    int a[maxn];

    void up(int p)
    {
        while(p>1)
        {
            if(a[p]<a[p/2])
                swap(a[p],a[p/2]),p=p/2;
            else break;
        }
    }

    void down(int p)
    {
        int s=p*2;
        while(s<=n)
        {
            if(s<n&&a[s]>a[s+1]) s++;
            if(a[s]<a[p]) swap(a[s],a[p]),p=s,s=p*2;
            else break;
        }
    }

    void push(int x)
    {
        a[++n]=x;
        up(n);
    }

    void pop(void)
    {
        a[1]=a[n--];
        down(1);
    }

    int top(void)
    {
        return a[1];
    }

}q;
int main(void)
{
    int n;
    int op,x;
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&op);
        if(op==1)
        {
            scanf("%d",&x);
            q.push(x);
        }
        else if(op==2)
            printf("%d\n",q.top());
        else q.pop();
    }
    return 0;
}

(5)冒泡排序

冒泡排序(Bubble Sort),是一种计算机科学领域的较简单的排序算法

它重复地走访过要排序的元素列,依次比较两个相邻的元素,如果顺序(如从大到小、首字母从Z到A)错误就把他们交换过来。走访元素的工作是重复地进行直到没有相邻元素需要交换,也就是说该元素列已经排序完成。

这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端(升序或降序排列),就如同碳酸饮料中二氧化碳的气泡最终会上浮到顶端一样,故名“冒泡排序”。

平均时间复杂度:O(n^2)

最坏时间复杂度:O(n^2)

最好时间复杂度:O(n^2)\ \ or\ \ O(n)  看处理方式

空间复杂度:O(1)

稳定性:稳定 

void bubbleSort(vector<int>& a)
{
	int len = a.size();
	for (int i = 0; i < len - 1; i++) //需要循环次数
	{
		for (int j = 0; j < len - 1 - i; j++) //每次需要比较个数
		{
			if (a[j] > a[j + 1])
			{
				swap(a[j], a[j + 1]); //交换
			}
		}
	}
}

(6)快速排序

快速排序算法通过多次比较和交换来实现排序,其排序流程如下: 

(1)首先设定一个分界值,通过该分界值将数组分成左右两部分。 

(2)将大于或等于分界值的数据集中到数组右边,小于分界值的数据集中到数组的左边。此时,左边部分中各元素都小于分界值,而右边部分中各元素都大于或等于分界值。 

(3)然后,左边和右边的数据可以独立排序。对于左侧的数组数据,又可以取一个分界值,将该部分数据分成左右两部分,同样在左边放置较小值,右边放置较大值。右侧的数组数据也可以做类似处理。 

(4)重复上述过程,可以看出,这是一个递归定义。通过递归将左侧部分排好序后,再递归排好右侧部分的顺序。当左、右两个部分各数据排序完成后,整个数组的排序也就完成了。 

平均时间复杂度:O(n log_2n)

最坏时间复杂度:O(n^2)

最好时间复杂度:O(nlog_2n)

空间复杂度:O(1)

稳定性:不稳定 


int a[maxn];

ll ra(void)
{
    int pos=rand()%4;
    ll ans=0;
    if(pos>=0) ans^=(1ll*rand());
    if(pos>=1) ans^=((1ll*rand())<<10);
    if(pos>=2) ans^=((1ll*rand())<<20);
    if(pos>=3) ans^=((1ll*rand())<<30);
    return ans;
}

ll ra(ll l,ll r)
{
    return ra()%(r-l+1)+l;
}

int kp(int *a,int l,int r)
{
	int i=l,j=r+1;
	swap(a[l],a[ra(l,r)]);
	while(true)
    {
		while(a[++i]<a[l]&&i<r) continue;
		while(a[--j]>a[l]) continue;
		if(i>=j) break;
		swap(a[i],a[j]);
    }
    swap(a[j],a[l]);
    return j;
}

void kSort(int *a,int l,int r)
{
	if(l>=r) return ;
    int mid=kp(a,l,r);
    kSort(a,l,mid-1);
    kSort(a,mid+1,r);
}

int main(void)
{
    srand(time(0));
    int n;
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i]);

    kSort(a,1,n);

	for(int i=1;i<=n;i++)
        printf("%d ",a[i]);
    putchar('\n');
    return 0;
}

找数组中第k大,借助快速排序思想,实现O(n)时间复杂度内找到第k大。


int a[maxn];

ll ra(void)
{
    int pos=rand()%4;
    ll ans=0;
    if(pos>=0) ans^=(1ll*rand());
    if(pos>=1) ans^=((1ll*rand())<<10);
    if(pos>=2) ans^=((1ll*rand())<<20);
    if(pos>=3) ans^=((1ll*rand())<<30);
    return ans;
}

ll ra(ll l,ll r)
{
    return ra()%(r-l+1)+l;
}

int kp(int *a,int l,int r)
{
	int i=l,j=r+1;
	swap(a[l],a[ra(l,r)]);
	while(true)
    {
		while(a[++i]<a[l]&&i<r) continue;
		while(a[--j]>a[l]) continue;
		if(i>=j) break;
		swap(a[i],a[j]);
    }
    swap(a[j],a[l]);
    return j;
}

int kth(int *a,int l,int r,int k)
{
	if(l>=r) return a[l];
    int mid=kp(a,l,r);
    if(mid-l+1==k) return a[mid];
    if(mid-l+1>k) return kth(a,l,mid-1,k);
	else return kth(a,mid+1,r,k-(mid-l+1));
}

int main(void)
{
    srand(time(0));
    int n,k;
    scanf("%d%d",&n,&k);
    k++;
    
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i]);

	printf("%d\n",kth(a,1,n,k));

    return 0;
}

(7)归并排序

        归并排序(Merge Sort)是建立在归并操作上的一种有效,稳定的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并

平均时间复杂度:O(nlog_2n)

最坏时间复杂度:O(nlog_2n)

最好时间复杂度:O(nlog_2n)

空间复杂度:O(n)

稳定性:稳定 

vector<int> mergeHelper(vector<int> &a, int left, int right)
{
    if (left == right) return vector<int> (1, a[left]);
    int mid = (right - left) / 2 + left;
    vector<int> l = mergeHelper(a, left, mid);
    vector<int> r = mergeHelper(a, mid + 1, right);
    //合并
    vector<int> ret;
    int ll = 0, rr = 0;
    while (ll < l.size() && rr < r.size())
    {
        if (l[ll] <= r[rr])     ret.push_back(l[ll++]);
        else                    ret.push_back(r[rr++]);
    }
    while (ll < l.size()) ret.push_back(l[ll++]);
    while (rr < r.size()) ret.push_back(r[rr++]);
    return ret;
}

(8)计数排序

计数排序是一个非基于比较的排序算法,该算法于1954年由 Harold H. Seward 提出。它的优势在于在对一定范围内的整数排序时,它的复杂度为Ο(n+k)(其中k是整数的范围),快于任何比较排序算法。 当然这是一种牺牲空间换取时间的做法,而且当O(k)>O(n*log(n))的时候其效率反而不如基于比较的排序(基于比较的排序的时间复杂度在理论上的下限是O(n*log(n)), 如归并排序,堆排序)

平均时间复杂度:O(n+k)

最坏时间复杂度:O(n+k)

最好时间复杂度:O(n+k)

空间复杂度:O(n+k)

稳定性:稳定 

int* countSort(int* arr,int len)
{
	int max = arr[0];//记录数列的最大值
	int min = arr[0];//记录数列的最小值
	for(int i=0;i<len-1;i++)
	{
		if(arr[i]>max)
		{
			max = arr[i];
		}
		if(arr[i]<min)
		{
			min = arr[i];
		}
	}
	int K = max-min;//计算出数列最大最小值得差值
	int* couarr = new int[K+1];
	for(int i=0;i<len;i++)
	{
		couarr[arr[i]-min]++;//统计元素个数
	}
 
	int sum = 0;
	for(int i=0;i<K+1;i++)//统计数组做变形,后面的元素等于前面元素的和
	{
		sum += couarr[i];
		couarr[i]=sum;
	}
	int* sortarr = new int[len];
	for(int i=len-1;i>=0;i--)//倒序遍历原始数组,从统计数组中找到正确位置
	{
		sortarr[couarr[arr[i]-min]-1]=arr[i];
		couarr[arr[i]-min]--;
	}
	delete couarr;
	return sortarr;
}

(9)桶排序

        桶排序 (Bucket sort)或所谓的箱排序,是一个排序算法,工作的原理是将数组分到有限数量的桶子里。每个桶子再个别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排序)。桶排序是鸽巢排序的一种归纳结果。当要被排序的数组内的数值是均匀分配的时候,桶排序使用线性时间(Θn))。但桶排序并不是 比较排序,他不受到 O(n log n) 下限的影响。

        简单的思想:划分多个范围相同的区间,每个子区间自排序,最后合并
        桶排序是计数排序的扩展版本,计数排序可以看成每个桶只存储相同元素,而桶排序每个桶存储一定范围的元素,通过映射函数,将待排序数组中的元素映射到各个对应的桶中,对每个桶中的元素进行排序,最后将非空桶中的元素逐个放入原序列中。
步骤:
        根据待排序集合中最大元素和最小元素的差值范围和映射规则,确定申请的桶个数;
        遍历待排序集合,将每一个元素移动到对应的桶中;
        对每一个桶中元素进行排序,并移动到已排序集合中。

不考虑映射,桶排序整数:

平均时间复杂度:O(n+k)

最坏时间复杂度:O(n+k)

最好时间复杂度:O(n+k)

空间复杂度:O(n+k)

稳定性:稳定 

void bksort(float A[],int l,int h){
    int size = h-l+1;
    vector<float> b[size];//有size个数据,就分配size个桶
    for(int i=l;i<=h;i++){
        int bi = size*A[i];//元素A[i]的桶编号
        b[bi].push_back(A[i]);//将元素A[i]压入桶中
    }
    for(int i=0;i<size;i++)
        sort(b[i].begin(),b[i].end());//桶内排序
    int idx = l;//指向数组A的下标
    for(int i=0;i<size;i++){//遍历桶
        for(int j=0;j<b[i].size();j++){//遍历桶内元素
            A[idx++] = b[i][j];
        }
    }
}


(10)基数排序

        基数排序(radix sort)属于“分配式排序”(distribution sort),又称“桶子法”(bucket sort)或bin sort,顾名思义,它是透过键值的部份资讯,将要排序的元素分配至某些“桶”中,藉以达到排序的作用,基数排序法是属于稳定性的排序,其时间复杂度为O ((n+r)  * d)), 其中n为元素个数,r为键值取值范围,d为键值个数。 在某些时候,基数排序法的效率高于其它的稳定性排序法。

平均时间复杂度:O((n+r)*d)

最坏时间复杂度:O((n+r)*d)

最好时间复杂度:O((n+r)*d)

空间复杂度:O(r+n)

稳定性:稳定 

/*
*求数据的最大位数,决定排序次数
*/
int maxbit(int data[], int n) 
{
    int d = 1; //保存最大的位数
    int p = 10;
    for(int i = 0; i < n; ++i)
    {
        while(data[i] >= p)
        {
            p *= 10;
            ++d;
        }
    }
    return d;
}
void radixsort(int data[], int n) //基数排序
{
    int d = maxbit(data, n);
    int tmp[n];
    int count[10]; //计数器
    int i, j, k;
    int radix = 1;
    for(i = 1; i <= d; i++) //进行d次排序
    {
        for(j = 0; j < 10; j++)
            count[j] = 0; //每次分配前清空计数器
        for(j = 0; j < n; j++)
        {
            k = (data[j] / radix) % 10; //统计每个桶中的记录数
            count[k]++;
        }
        for(j = 1; j < 10; j++)
            count[j] = count[j - 1] + count[j]; //将tmp中的位置依次分配给每个桶
        for(j = n - 1; j >= 0; j--) //将所有桶中记录依次收集到tmp中
        {
            k = (data[j] / radix) % 10;
            tmp[count[k] - 1] = data[j];
            count[k]--;
        }
        for(j = 0; j < n; j++) //将临时数组的内容复制到data中
            data[j] = tmp[j];
        radix = radix * 10;
    }
}
 
 
 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值