十种排序算法详解,一篇文章搞定

基础算法

冒泡排序

两两元素依次进行比较,如果后面的元素比前面的小,说明顺序不对(这里是从最后开始往前进行),两个元素进行交换,循环进行,直到没有元素进行交换,结束循环
缺点:效率低 O(N^2)
冒泡

#include<cstdio>
using namespace std;

//交换数组的中的元素,int b,int c为元素在数组中的下标 
void Swap(int a[],int b,int c)
{		
	int temp = a[b];
	a[b] = a[c];
	a[c] = temp;
}

// int n为数组的大小 
void BubbleSort1(int arr[],int n)
{
	for (int i=0;i<n;i++)
	{
		for (int j=n-1;j>i;j--)
		{
			if (arr[j] < arr[j-1])
				Swap(arr,j,j-1);	
		}
	}
}

冒泡排序的优化

void BubbleSort2(int arr[],int n)
{
	// flagw为循环标志,如果i下面没有发生交换
	// 证明下面的顺序是对着的,不需要进行排序了
	int flag = 1;
	
	for (int i=0;i<n&&flag;i++)
	{
		flag = 0;
		for (int j=n-1;j>i;j--)
		{
			if (arr[j] < arr[j-1])
			{
				Swap(arr,j,j-1);
				flag = 1;	
			}
		}
	}
}

选择排序

通过N-i次关键字间的比较,从N-i+1个记录中选出关键字中最小的记录
选择排序
效率低 O(N^2)
经常用它的内部循环方式求数组的最大值和最小值

#include<cstdio>
using namespace std;

//交换数组的中的元素,int b,int c为元素在数组中的下标 
void Swap(int a[],int b,int c)
{		
	int temp = a[b];
	a[b] = a[c];
	a[c] = temp;
}

// 选择排序
// int n为数组的大小 
void SelectSort(int arr[],int n)
{
	int min;
	for (int i=1;i<n;i++)
	{
		min = i;
		for (int j=i+1;j<n;j++)
		{
			if (arr[j] < arr[min])
			{
				min = j;
			}
		}
		if (i != min) Swap(arr,i,min);
	}	
} 

插入排序

每一步将一个待排序的数据插入到前面已经排好序的有序序列中,直到插完所有元素为止
效率不高,O(N^2)
但是在序列基本有序时,它很快,他也有其适用范围
插入排序
用循环实现

#include<cstdio>
using namespace std;

// 插入排序,用循环实现 
void InsertSort(int arr[],int n)
{
	// 因为数组arr[0]一个元素本身就是有序的,所以从arr[1]开始
	// 将后面无序序列中的一个记录逐个插入到前面的有序序列中 
	for (int i=1;i<n;i++)
	{
		// 如果记录比有序序列最后一个元素小 
		if (arr[i] < arr[i-1])
		{
			// 将记录放置到辅助位置上 
			int temp = arr[i];
			// 将记录与前一个元素逐个进行比较 
			for (int j=i-1;i>=0;i--)
			{
				// 如果记录比前一个元素小,交换位置 
				if (arr[j+1] < arr[j])
					arr[j+1] = arr[j];
				else
					// 直到记录没有最后一个元素小,将记录放到当前位置 
					arr[j] = temp;	
			}
		}		
	}
}

用递归实现

/** 
 * 这个插入排序时正确的
 * int arr[] 为数组名
 * int n 前有序数列的元素个数 
 * 将最后一个元素与前面已经排好顺序的数组做插入排序 
 *
 */
#include<cstdio>
using namespace std;

// 用递归实现插入排序
// 将第n个元素插入到前面的有序数列中 
void insertsort(int arr[],int n)
{
	// 就一个元素了,不需要再做比较了 
	if (n==1) return;
	
	// 递归实现,将第n-1个元素插入到前面的有序数列中 
	insertsort(arr,n-1);
	int x = arr[n-1]; // 最后一个元素的值 
	int index=n-2; // 前一个元素的“指针” 
	
	while(index>-1&&x<arr[index])
	{
		// 将元素后移一位 
		arr[index+1] = arr[index];
		index--;
	}
	arr[index+1] = x;
}

希尔排序

前面我们知道,序列基本有序时或者记录数比较少时插入排序的效率还是很高的。而希尔排序就是插入排序的改进版本,又称缩小增量排序。
我们可以将原本有大量数据的序列进行分组,分成若干个小组后在各个小组内进行插入排序,这时各个小组就基本有序了,然后再对全体进行一次插入排序。
分组我们可以采取跳跃分割的方法:每次按n/2的间隔进行分组
效率准确为O(nlgn) - O(n^2) 有最好和最坏情况
效率大约为O(n^1.3)
希尔排序

#include<cstdio>
using namespace std;

// 希尔排序,int n为数组的大小 
void ShellSort(int arr[],int n)
{
	// 不断缩小的增量
	for (int i=(n/2);i>=1;i=i/2)
	{
		// 插入排序
		for (int j=i;j<n;j++)
		{
			int a=arr[j];    // 辅助元素 
			int index = j-i; // 同一小组元素的索引 
			while (index>-1&&a<arr[index])
			{
			arr[index+i] = arr[index];
				index -= i;
			}
			// 因为循环内最后还执行了一次index-i,所以要加i 
			arr[index+i] = a;
		}						
	}			
}

分治法

快速排序

基本思想:通过一趟排序将待排记录分割成独立的两部分,其中一部分记录的关键字均比另一部分关键字小,则可以分别对这两部分记录记录继续进行排序,以达到整个序列有序的目的
就是使用分治法分而治之,选取一个主元,将数组左边的部分替换为比主元小的元素,右边为大的元素,在使用快速分别对比主元小的元素和大的元素进行排序,递归下去,最后这个序列就会变为有序的序列
快排是软件工业中最常见的常规排序法,其** 双向指针扫描 **和 **分区算法 **是核心,往往用于解决类似问题,特别partition用来划分不同性质的元素

单指针分区
快排-0
快排-1

#include<cstdio>
using namespace std;

// 交换元素函数
// int b,int c为所要交换的元素的数组下标 
void Swap(int arr[],int b,int c)
{
	int temp    = arr[b];
	    arr[b]  = arr[c];
	    arr[c]  = temp;
}

// 单指针分区函数,函数返回主元在数组的下标 
// int p,int r为元素位置,不是数组下标 
int Partition(int arr[],int p,int r)
{
	int povit = arr[p-1]; // 定义主元
	int sp = p; // 定义与主元比较元素指针 
	int bigger = r-1; // 定义较大值的指针
	
	while (sp<=bigger)
	{
		if (arr[sp]<=povit) { sp++; } 
		else { Swap(arr,sp,bigger); bigger--; }			
	}
	Swap(arr,p-1,bigger);
	
	return bigger;		
}

// 快速排序函数
// int p为起始元素位置,int r为最后元素的位置 
void QuickSort(int arr[],int p,int r)
{
	if (p<r)
	{
		// k为主元在数组中的下标,从p开始 
		int k = Partition(arr,p,r);
		QuickSort(arr,p,k);
		QuickSort(arr,k+2,r);
	}
} 

双指针分区
快速排序-2

// 对数字进行划分
// int p 为数组起始位置,int r 为数组结束位置
// 例如:从第一个元素排到第五个元素为QuickSort(a,1,5);
// 函数返回主元在数组中的下标 
int Partition(int arr[],int p,int r)
{
	int povit = arr[p-1];
	int sp    = p;
	int bigger= r-1;
	
	while (sp<=bigger)
	{
		if (arr[sp] <= povit) { sp++; }
		else if (arr[bigger] > povit){ bigger--; }
		else { Swap(arr,sp,bigger); }	
	}
	Swap(arr,p-1,bigger);
	
	return bigger;	
}

// 对数组进行排序
// int p 为数组起始位置,int r 为数组结束位置的
// 例如:从第一个元素排到第五个元素为QuickSort(a,1,5); 
void QuickSort(int arr[],int p,int r)
{	
	if (p<=r)
	{
		int k = Partition(arr,p,r);
		QuickSort(arr,p,k);
		QuickSort(arr,k+2,r);
	}
} 

三指针分区
快速排序-3.1
快速排序-3.2

struct Key
{
	int equ;
	int bigger;
}key;

// 对数字进行划分
// int p 为数组起始位置,int r 为数组结束位置
// 例如:从第一个元素排到第五个元素为QuickSort(a,1,5);
// 函数返回主元在数组中的下标 
struct Key Partition(int arr[],int p,int r)
{
	int povit = arr[p-1];
	int sp    = p;
	int bigger= r-1;
	int equ   = p;
	
	while (sp<=bigger)
	{
		if (arr[sp] < povit) { Swap(arr,sp,equ); sp++; equ++; }
		else if(arr[sp] == povit) { sp++; }
		else if (arr[bigger] > povit){ bigger--; }	
		else { Swap(arr,sp,bigger); }	
	}
	Swap(arr,p-1,equ-1); equ--;
	key.bigger = bigger;
	key.equ    = equ;
	
	return key;	
}

// 对数组进行排序
// int p 为数组起始位置,int r 为数组结束位置的
// 例如:从第一个元素排到第五个元素为QuickSort(a,1,5); 
void QuickSort(int arr[],int p,int r)
{	
	if (p<=r)
	{
		Partition(arr,p,r);
		QuickSort(arr,p,key.equ);
		QuickSort(arr,key.bigger+2,r);
	}
} 

归并排序

将一个又n个元素的序列分成含有n/2个元素的子序列,再对两个子序列进行递归排序,将两个已经排好序的子序列进行合并
归并排序-1
归并排序-2

int helper[1024];
// 归并 
void Marge(int arr[],const int p,int mid,const int r)
{
	// 将arr中的数据拷贝到aa中的相同位置 
	memcpy(helper+(p-1),arr+(p-1),sizeof(int)*(r-p+1));  // 最关键的点 
	int left    = p-1;   // 左侧队伍的头部指针,指向带比较的元素 
	int right   = mid;   // 右侧队伍的头部指针,指向带比较的元素,mid位置所在的元素属于左部分 
	int current = p-1;   // 原数组的指针,指向待填入数据的位置 
	
	// 不需要等于号,left、right为指针,mid、r为元素位置 
	while (left < mid && right < r)
	{
		if (helper[left] <= helper[right])
		{
			arr[current] = helper[left];
			left++;
			current++;			
		}
		else
		{
			arr[current] = helper[right];
			right++;
			current++;			
		}	
	} 
	while (left<mid)
	{
		arr[current] = helper[left];
		left++;
		current++;
	}					
}

// 归并排序
// int p,int r为元素在数组中的位置,不是下标,使用时应该减 
void MargeSort(int arr[],int p,int r)
{
	if (p<r)
	{
		// int mid元素在数组中的位置,不是下标,使用时应该减一 
		int mid =p+((r-p)>>1);
		MargeSort(arr,p,mid);
		MargeSort(arr,mid+1,r);
		Marge(arr,p,mid,r);		
	}	
} 

利用数据结构

堆排序

完全二叉树:初叶子结点外,每个结点都有两个子结点
堆是具有以下性质的完全二叉树:
每个结点的值都大于或等于其左右孩子值,称为大顶堆
每个结点的值都小于或等于其左右孩子值,称为小顶堆
用数组来实现堆(任意一个结点i,他的左孩子为2i+1,右孩子为2i+2)

堆排序(从小到大为例
先将待排序的序列构造成一个大顶堆,那么堆顶的根结点就是最大值,将他与最末尾的元素交换,则最大值就到了尾部,我们再将剩下的n-1个序列按上面的步骤反复执行,就可以得到一个有序序列

先将序列堆化
就是将从n/2个结点开始,使每一个子树都为大顶堆
堆化
选取(9/2=4)第四个结点开始堆化,选取较小一个的第三个结点开始堆化,如果发生了交换,就要将交换时较小的那个值作为结点递归进行堆化
堆化0

78大于任何一个孩子,不需要调整
堆化1
4小于下面任何一个数,取下面最大的数进行交换
堆化3
交换后取4所在的结点进行递归堆化,结点叶子结点,所以结束不用堆化
堆化2.1
选第2个结点进行堆化,23与78交换

堆化2
以23所在的位置进行递归堆化,23和43交换位置
堆化4.1
23为叶子结点所以结束本次堆化,退会到结点1进行堆化
以结点1进行堆化时,2与78交换位置
堆化5.2

以2所在的位置递归堆化
堆化5.3

2与23进行交换,以2所在的位置递归堆化
堆化5.4
2和23交换,以2所在的位置递归,2为叶子结点,堆化完成
堆化5.5

推排序
将最前面与最后面的交换位置,再堆剩下的n-1个元素进行堆化,堆化后再堆排序,如此循环下去
将78与15进行交换,堆除25外的剩下8个元素进行堆化,堆排序
堆化6

#include<cstdio>
using namespace std;

void Swap(int a[],int c,int d)
{
	int temp;
	temp = a[c];
	a[c] = a[d];
	a[d] = temp;
}		

// 调整以i为根节点子树中的元素位置 
// int i为子树根节点的下标,int n为数组的大小 
void MaxHeapFixDown(int arr[],int i,int n)
{
	int left = 2*i+1;  // 左孩子
	int right= 2*i+2;  // 右孩子
	
	// 左孩子已经越界,i为叶子结点 
	if (left >= n) return;
	
	// 找到左右孩子中的最大值
	int max = left;
	// 右孩子越界,只剩下左孩子那左孩子必然就是最大值 
	if (right >= n) max = left;
	else if (arr[right] > arr[max]) max = right;
	
	// 如果arr[i]>arr[max],不用交换
	if (arr[i] > arr[max]) return;
	// 否则,将孩子中的最大元素与i交换
	Swap(arr,i,max);
	
	// 孩子中较大的元素位置发生了改变,递归调整子树
	MaxHeapFixDown(arr,max,n); 	 			
}

// 堆化 
// int n为数组的大小 
void MaxHeap(int arr[],int n)
{
	for (int i=(n/2-1);i>=0;i--)
		MaxHeapFixDown(arr,i,n);
}

// 堆排序
void HeapSort(int arr[],int n)
{
	// 堆arr进行堆化
	MaxHeap(arr,n);
	// 对堆的元素进行交换
	for (int i=n-1;i>0;i--)
	{
		// 将堆顶的元素和最后一个元素交换 
		Swap(arr,0,i);
		// 缩小堆的范围,对堆顶元素进行向下调整
		MaxHeapFixDown(arr,0,i); 	
	 } 	
} 

以上排序都是基于比较的排序,可证明它们在元素随机顺序情况下最好的是Nlg(N)
快排、归并、堆排的复杂度都是Nlg(N),其中表现最好的是快排,它是原址的不用开辟辅助空间,堆排也是原址的,但是堆排的常数因子较大,不具备优势

以下为非比较排序,在特定情况下会比基于比较的排序要快

计数排序

计数排序就是开辟一个辅助数组,将数组中的元素转换为辅助数组中的下标,假设元素均大于等于0,依次扫描原数组,将元素值记录在辅助数组的K为上,再依次扫描辅助数组,如果值为1,将下标的值插入到原原数组的对应位置。
计数排序属于用空间来换时间
如果用它解决问题时数列中的值分布非常广(最大值很大,元素分布很稀疏),空间将浪费很多
计数排序比较适用于–序列的关键字比较集中,已知边界且边界较小–

#include<cstdio>
#include<cstring>
using namespace std;

const int len = 1024;
int A[len];

// 计数排序,辅助空间为1024,所以只能对小于1024的数字排序 
void CountSort(int arr[],int n)
{
	memset(A,0,sizeof(int)*1024);
	
	for (int i=0;i<n;i++)
	{
		A[arr[i]]++;		
	}
	int k=0; //数据回填的位置 
	for (int j=0;j<len;j++)
	{
		while (A[j] > 0)
		{
			arr[k++] = j;
			A[j]--;
		}
	}
}

桶排序

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值