排序算法总结

冒泡排序

冒泡排序的本质在于交换,即每次通过交换的方式把当前剩余元素的最大值移动到一端,而当剩余元素减少为0时,排序结束。冒泡排序(Bubble Sort)是一种简单直观的排序算法。

步骤:

  1. 比较相邻的元素。如果第一个比第二个大,就交换他们两个。

  2. 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。

  3. 针对所有的元素重复以上的步骤,除了最后一个。

  4. 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。

代码实现:

void bubbleSort(int arr[])
{
    for (int i = 0; i < n - 1; i ++ )
    {
        for (int j = i; j < n - 1; j ++ )
        {
            if(arr[j] > arr[j + 1])
            {
                int temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
            }
        }
    }
}

选择排序

选择排序是最简单的排序算法之一,主要介绍众多选择排序方法中最常用的简单选择排序。如图所示,简单选择排序是指,对一个序列A中的元素A[0]~A[n-1],令i从0到n枚举,进行n趟操作,每趟从待排序部分[i,n-1]中选择最小的元素,令其与待排序部分的第一个元素A[i]进行交换,这样元素A[i]的就会与当前有序区间[1,i-1]形成新的有序区间[1,i]。于是在n趟操作后,所有元素就会是有序的。

在这里插入图片描述

步骤:

  1. 首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置。

  2. 再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。

  3. 重复第二步,直到所有元素均排序完毕。

代码实现:

void selectSort(int arr[])
{
    for (int i = 0; i < n; i ++ )
    {
        int min = i;		//令最小下标为min
        for (int j = i + 1; j < n; j ++ )
        {
            if(arr[min] > arr[j])
            {
                min = j;		//更新最小下标
            }
        }
        //交换
        int temp = arr[min];
        arr[min] = arr[i];
        arr[i] = temp;
    }
}

插入排序

插入排序也是最简单的一类排序方法,该算法与打扑克整理手牌顺序的原理类似。本节主要介绍众多插入排序方法中最直观的直接插入排序。直接插入排序是指,对序列A的n个元素A[0]~A[n-1],令i从1到n-1枚举,进行
n-1趟操作。假设某一趟时,序列A的前i-1个元素A[0]~A[i-1]已经有序,而范围[i,n-1]
还未有序,那么该趟从范围[0,i-1]中寻找某个位置j,使得将A[i]插入位置j后(此时A[j]~
A[i-1]会后移一位至A[j+1]~A[i]),范围[0,i]有序。

步骤:

  1. 将第一待排序序列第一个元素看做一个有序序列,把第二个元素到最后一个元素当成是未排序序列。

  2. 从头到尾依次扫描未排序序列,将扫描到的每个元素插入有序序列的适当位置。(如果待插入的元素与有序序列中的某个元素相等,则将待插入元素插入到相等元素的后面。)

代码实现:

void insertSort(int arr[], int n)
{
    for (int i = 1; i < n; i ++ )
    {
        int temp = arr[i], j = i;
        while(j > 0 && temp < arr[j - 1])	
        {
            arr[j] = arr[j - 1];			//arr[j-1]后移
            j--;
        }
        arr[j] = temp;						//插入操作
    }
}

归并排序

双指针扫描法

此算法用于引入双指针的思想,该思想将同样用于后面的序列归并问题以及归并排序算法。

例:
给定一个递增的正整数序列和一个正整数M,求序列中的两个不同位置的数a和b,使得它们的和恰好为M,输出所有满足条件的方案。例如给定序列arr[]={1,2,3,4,5,6}和正整数M=8,就存在2+6=8与3+5=8成立。

分析:
最直接的方法是进行双重遍历,但其时间复杂度是O(n2),n超过105时就不可取了。所以采用时间复杂度为O(n)的双指针扫描算法。该算法使用两个数组下标变量i、j,分别指向数组的第一个和最后一个元素,下标i递增,下标j递减。注意这是一个递增序列,下面进一步分析:

  • 当arr[i]+arr[j]=M时,打印结果并且i,j下标同时移动。
  • 当arr[i]+arr[j]<M时,说明arr[i]+arr[j-1]<M也成立,此时要移动i下标,增大其相加的值。
  • 当arr[i]+arr[j]>M时,说明arr[i]+arr[j-1]>M也成立,此时要移动j下标,减小其相加的值。

代码实现:

while(i < j)
{
	if(arr[i] + arr[j] == M)
	{
		printf("%d %d\n", arr[i], arr[j]);
		i++;
		j--;
	}
	else if(arr[i] + arr[j] < M)
	{
		i++;
	}
	else
	{
		j--;
	}
}

此时i,j的总移动次数不会超过n,因此时间复杂度也就为O(n)。可见此算法避免了一些不必要遍历,及时剪枝,大大缩小了时间复杂度。

序列合并(merge)

例:

假设有两个递增序列A与B,要求将它们合并为一个递增序列C。

分析:

同样的,可以设置两个下标i和j,初值均为0,表示分别指向序列A的第一个元素和序列B的第一个元素,然后根据A[i]与B[j]的大小来决定哪一个放入序列C。

  • 若A[i]的<B[j],说明A[i]的是当前序列A与序列B的剩余元素中最小的那个,因此把A[i]加入序列C中,并让i加1(即让i右移一位)。
  • 若A[i]>B[j],说明B是当前序列A与序列B的剩余元素中最小的那个,因此把
    B们加入序列C中,并让j加1(即让j右移一位)。
  • 若A[i]=B[j],则任意选一个加入到序列C中,并让对应的下标加1。
    上面的分支操作直到i、j中的一个到达序列末端为止,然后将另一个序列的所有元素依次加入序列C中。

代码实现:

int merge(int A[], int B[], int C[], int n, int m)
{
	int i = 0, j = 0, index = 0;
	while(i < n && j < m)
	{
		if(A[i] < B[j])
			C[index++] = A[i++];
		else
			C[index++] = B[j++];
	}
	//将剩余元素加入序列C中
	while(i < n) C[index++] = A[i++];
	while(j < n) C[index++] = B[j++];
	return index;		//返回序列C的长度
}

归并排序

归并排序主要讨论2路归并排序,例如将序列A[]={66,12,33,57,64,27,18}进行归并排序,如下图所示,其主要思想是将序列两两分组直到只剩下一组,组内进行单独排序。归并排序的时间复杂度是O(nlogn)。
在这里插入图片描述

代码实现:

首先先写出每组的归并排序函数merge,其实现方式和以上介绍的序列合并基本相同。
数组A的左区间范围是[L1,R1],右区间范围是[L2,R2],其中L2=R1+1

const int max = 100;
void merge(int A[], int L1, int R1, int L2, int R2)
{
	int i = L1, j = L2;
	int temp[max], index = 0;
	while(i <= R1 && j <= R2)
	{
		if(A[i] < A[j])
			temp[index++] = A[i++];
		else
			temp[index++] = A[j++];
	}
	while(i <= R1)	temp[index++] = A[i++];
	while(j <= R2)	temp[index++] = A[j++];
	for(int i = 0; i < index; i++)
	{
		A[L1 + i] = temp[i];
	}
}

接下来写出对整个序列进行2路归并排序的递归代码

void mergeSort(int A[], int left, int right)
{
	if(left < right)
	{
		int mid = (left + right) / 2;
		mergeSort(A, left, mid);
		mergeSort(A, mid + 1, right);
		merge(A, left, mid, mid + 1, right);	//左子区间与右子区间合并
	}
}

快速排序

快速排序是排序算法中平均时间复杂度为O(nlogn)的一种算法,其实现需要先解决这样一个问题:对一个序列A[1]、A[2]、…、A[n],调整序列中元素的位置,使得A1的左侧所有元素都不超过A[1]、右侧所有元素都大于A[1]。例如对序列{5,3,9,6,4,1}来说,可以调整序列中元素的位置,形成序列{3,1,4,5,9,6},这样就让A[1]=5左侧的所有元素都不超过它、右侧的所有元素都大于它。

步骤:

  1. 从数列中挑出一个元素,称为 “基准”(pivot);

  2. 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;

  3. 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序;

分区代码:

int partition(int arr[], int left, int right)
{
    int temp = arr[left];    //将arr[left]作为主元
    while(left < right)
    {
        while(left < right && arr[right] > temp)    right--;
            arr[left] = arr[right];
        while(left < right && arr[left] <= temp)    left++;
            arr[right] = arr[left];
    }
    arr[left] = temp;
    return left;            //返回相遇的位置
}

快排代码:

void quickSort(int arr[], int left, int right)
{
    if(left < right)
    {
        int pos = partition(arr, left, right);
        quickSort(arr, left, pos - 1);		//左区间递归进行快排
        quickSort(arr, pos + 1, right);		//右区间递归进行快排
    }
}

此类题目汇总

  1. 【AcWing 785】快速排序
  2. 【PAT A 1025】PAT Ranking (结构体排序)
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值