十大经典排序算法

十大经典排序算法

         排序算法可以分为内部排序和外部排序。内部排序是数据记录在内存中进行排序。而外部排序是因排序的数据很大,一次不能容纳全部的排序记录,在排序过程中需要访问外存。

         常见的内部排序算法有:插入排序、希尔排序、选择排序、冒泡排序、归并排序、快速排序、堆排序、基数排序等。

十大经典排序算法_第1张图片

关于时间复杂度:

  1. 平方阶(O(n2))排序各类简单排序:直接插入、直接选择和冒泡排序。
  2. 线性对数阶(O(nlog2n))排序:快速排序、堆排序和归并排序。
  3. O(n1+$)排序,$是介于0和1之间的常数。  希尔排序
  4. 线性阶(O(n))排序:基数排序,此外还有桶、箱排序。

关于稳定性:

  1. 稳定的排序算法:冒泡排序、插入排序、归并排序和基数排序。
  2. 不稳定的排序算法:选择排序、快速排序、希尔排序、堆排序。

一、冒泡排序

1.1 算法步骤

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

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

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

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

1.2 代码

int* Sort(int* arr, int length){
	for(int i=1; i < length; i++){
		//设定一个标记,若为true,则表示此次循环没有进行交换,也就是待排序列已经有序,排序已经完成
		bool flag = true;
		for(int j=0; j < length-i; j++){
			if(arr[j] > arr[j+1]){
				int tmp = arr[j];
				arr[j] = arr[j+1];
				arr[j+1] = tmp;
				flag=false;
			}
		}
		if(flag){
			break;
		}
	}
	return arr;
}

二、选择排序

2.1 算法步骤

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

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

(3)重复(2),直到所有元素均排序完毕。

2.2 代码

int* sort(int* sourceArray, int length){
	int* arr = sourceArray;
	//总共要经过N-1轮比较
	for(int i=0; i < length; i++){
		int min = i;
		//每轮需要比较的次数N-1
		for(int j=i+1; j < length; j++){
			if(arr[j] < arr[min]){
				//记录目前能找到的最小值元素的下标
				min = j;
			}
		}
		//将找到的最小值和i位置所在的值进行交换
		if(i != min ){
			int tmp = arr[i];
			arr[i] = arr[min];
			arr[min] = tmp;
		}
	}
	return arr;
}

三、插入排序

3.1 算法步骤

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

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

3.2代码

template 
void InsertSort(T arr, int length){
	//从下标为1的元素开始选择合适的位置插入,因为下标为0的只有一个元素,默认是有序的
	for(int i=1; i0 && key

四、希尔排序

4.1 算法步骤

基本思想是:先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录“基本有序”时,再对全体记录进行依次直接插入排序。

(1)选择一个增量序列t1,t2,…,tk,其中ti>tj,tk=1;

(2)按增量序列个数k,对序列进行k 趟排序;

(3)每趟排序,根据对应的增量ti,将待排序列分割成若干长度为m的子序列,分别对各子表进行直接插入排序。仅增量因子为1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。

4.2 代码

template 
void shellSort(T arr[], int len){
	int gap;
	for(gap = len/2; gap > 0; gap /= 2){ //增量序列为n/2,n/4...直到1
		for(int i=0; i arr[j]){
					int tmp = arr[j];
					int k = j-gap;
					while(k>=0 && arr[k]>tmp){
						arr[k+gap] = arr[k];
						k = k-gap;
					}
					arr[k+gap] = tmp;
				}
			}
		}
	}
}

五、归并排序

5.1 算法步骤

(1)申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列。

(2)设定两个指针,最初位置分别为两个已经排序序列的起始位置。

(3)比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置。

(4)重复步骤(3)直到某一指针达到序列尾。

(5)将另一序列剩下的所有元素直接复制到合并序列尾。

5.2 代码

void mergeSort(vector &data, int start, int end){
	if(start < end){
		int mid = (start + end) / 2;
		mergeSort(data, start, mid);//分治思想
		mergeSort(data, mid+1, end);
		merge(data, start, mid, end);
	}
}
//合并
void merge(vector &data, int start, int mid, int end){
	vector tmp;
	int i=start, j=mid+1;
	while(i != mid+1 && j != end+1){
		if(data[i] <= data[j]){
			tmp.push_back(data[i++]);
		}else{
			tmp.push_back(data[j++])
		}
	}
	while(i <= mid+1){
		tmp.push_back(data[i++]);
	}
	while(j <= end+1){
		tmp.push_back(data[j++]);
	}
	for(int i=0; i < tmp.size(); i++){
		data[start+i] = tmp[i];
	}
}

六、快速排序

6.1 算法步骤

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

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

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

6.2 代码

//分治思想,第一个元素当基准,分成两个有序子序列
int Partition(vector &data, int low, int up){
	int pivot = data[low];//基准
	while(low < up){
		while((low < up) && (data[up] >= pivot)){
			up--;//比基准大,本来就在右边,所以up后移
		}
		swap(data[low], data[up]);
		while((low < up) && (data[low] <= pivot)){
			low++;//比基准小,本来就在左边,所有low前移
		}
		swap(data[low], data[up]);
	}
	return low;//返回基准的位置
}
//快速排序
void quickSort(vector &data, int low, int up){
	if(low < up){
		int mid = Partition(data, low, up);
		quickSort(data, low,mid-1);//对左半段排序
		quickSort(data, mid, up);//对右半段排序
	}
}

七、堆排序

7.1 算法步骤

(1)创建一个堆H[0……n-1];

(2)把堆首(最小值)和堆尾(最后一个元素)互换,调整成堆,输出堆顶元素;

(3)把堆的尺寸缩小1,并调用shift_down(0),目的是把新的数组顶端数据调整到相应位置;

(4)重复步骤(2),直到堆得尺寸为1。

7.2 代码

//交换函数
void Swap(int &a, int &b){
	int tmp = a;
	a = b;
	b = tmp;
}
//堆排序的核心是建堆,传入参数为数组,根节点位置,数组长度
void heapBuild(int arr[], int root, int len){
	int lchild = root*2+1;//根节点的左子节点下标
	if(lchild < len)//左子节点下标不能超出数组的长度
	{
		int flag = lchild;//flag保存左右节点中最大值的下标
		int rchild = lchild+1;//根节点的右子节点下标
		if(rchild < len)//右子节点下标不能超出数组的长度(如果有的话)
		{
			if(arr[rchild] > arr[flag])//找出左右子节点中的最大值
			{
				flag = rchild;
			}
		}
		if(arr[root] < arr[flag])
		{
			//交换父节点和比父节点大的最大子节点
			Swap(arr[root], arr[flag]);
			//从此次最大子节点的那个位置开始递归建堆
			heapBuild(arr, flag, len);
		}
	}
}
//堆排序
void heapSort(int arr[], int len){
	for(int i=len/2; i>=0; i--){//从最后一个非叶子节点的父节点开始建堆
		heapBuild(arr,i.len);
	}
	for(int j=len-1; j>0; j--){//j表示数组此时的长度,因为len长度已经建过了,从len-1开始
		Swap(arr[0],arr[j]);//交换首尾元素,将最大值交换到数组的最后位置保存
		heapBuild(arr,0,j);//去除最后位置的元素重新建堆,此处j表示数组的长度,最后一个位置下标变为len-2
	}
}

八、计数排序

8.1 算法步骤

(1)花O(n)的时间扫描一下整个序列A,获取最小值min和最大值max;

(2)开辟一块新的空间创建新的数组B,长度为(max-min+1);

(3)数组B中index的元素记录的值是A中某元素出现的次数;

(4)最后输出目标整数序列,具体的逻辑是遍历数组B,输出相应元素以及对应的个数。

8.2 代码

void countSort(vector &vec, vector &objvec){
	vector range(10,0);//range的下标即键值
	
	for(int i=0; i=0; i--){//注意一个小细节,统计时时正序的,这里是逆序
		//如果存在相同的键值,为了保持稳定性,后出现的应该还是位于后面
		//如果正序,则先出现的会放置到后面,因此不再稳定
		objvec[range[vec[i]]]=vec[i];//将键值放到目标位置
		range[vec[i]]--;
	}
}

九、桶排序

9.1 算法步骤

(1)设置固定数量的空桶;

(2)把数据放到对应的桶中;

(3)对每个不为空的桶中数据进行排序;

(4)拼接不为空的桶中数据,得到结果。

9.2 代码

void bucketSort(int arr[], int n){
	vector b[n];//创建n个桶
	for(int i=0; i

十、基数排序

10.1 算法步骤

(1)将所有待比较数值(正整数)统一为同样的数位长度,数位较短的数前面补零;

(2)从最低为开始,依次进行一次排序;

(3)从最低位排序一直到最高位排序完成以后,数列就变成一个有序序列。

10.2 代码

//获取数组的最大元素
int getMax(int arr[], int n){
	int max = arr[0];
	for(int i=1; i max){
			max=arr[i];
		}
	}
	return max;
}
//利用计数排序对数组arr进行排序
void countSort(int arr[], int n, int exp){
	int output[n], i, count[10]={0};
	//统计每个键值出现的次数
	for(i=0; i=0; i--){
		output[count[(arr[i]/exp)%10]-1]=arr[i];//将键值放到目标位置
		count[(arr[i]/exp)%10]--;
	}
	for(i=0; i0; exp *=10){
		countSort(arr, n, exp);
	}
}

 

你可能感兴趣的:(算法设计)