常见的排序

常见的排序算法

在这里插入图片描述

插入排序

基本思想:
把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,得到一个新的有序序列 。

1. 直接插入排序

当插入第i(i>=1)个元素时,前面的array[0],array[1],…,array[i-1]已经排好序,此时用array[i]的排序码与array[i-1],array[i-2],…的排序码顺序进行比较,找到插入位置即将array[i]插入,原来位置上的元素顺序后移。(默认数组第一个元素已经完成元素插入)
在这里插入图片描述
例:插入1,1<=3,3后移;1<=2,2后移;1放入2的位置。
tip:
(记得用临时变量保存待插入元素的值,否则会在元素后移时被覆盖)

算法分析:

应用场景:数据接近有序或数据量比较少
平均时间复杂度:O(N^2),标准的内外两层循环
最好的时间复杂度:O(N),若有序,则每个元素不需要再找待插入元素的位置,只需要遍历1遍。
最坏的时间复杂度:O(N^2)
空间复杂度:O(1),需要1个临时变量保存待插入元素的值
稳定性:稳定

代码:

void InsertSort(int* a, int n){
	//对剩余的n-1个元素进行插入排序
	for (int i = 1; i < n; i++){
		//用temp记录待插入的元素,待会儿会被覆盖
		int temp = a[i];
		//end记录已排好序的最后的一个元素
		int end = i - 1;
		//找待插入元素的位置
		while (end >= 0 && a[end]>temp){
			a[end + 1] = a[end];
			end--;
		}
		//插入元素
		a[end + 1] = temp;
	}
}

2. 希尔排序

在这里插入图片描述

算法思想:
考虑对数据进行分组,将数据进行一定的间隔分组,每个数组应用插入排序。

先选定一个整数gap,把所有距离为gap的记录分在同一组内,并对每一组内的记录进行排序。重复上述分组和排序的工作。当gap到达=1时,所有记录在统一组内排好序。
在这里插入图片描述

代码:

//在插入排序的基础上实现的
void ShellSort(int* a, int n){
	assert(a);
	int gap = n;
	//当gap到达=1时,所有记录在统一组内排好序。
	while (gap > 1){
		gap = gap / 3 + 1;
		//分组+对每组进行插入排序
		for (int i = gap; i < n; i++){
			int temp = a[i];
			int end = i - gap;
			while (end >= 0 && a[end]>temp){
				a[end + gap] = a[end];
				end-=gap;
			}
			a[end + gap] = temp;
		}
	}
}

算法分析:

平均时间复杂度:大量实验的基础上得到约为O(N^1.3), 而 Knuth提出在O(N^1.25)和 O(1.6*N^1.25)之间。
最好的时间复杂度:和增量序列的选取有关。
最坏的时间复杂度:O(N^2)
空间复杂度:O(1)
应用场景:数据量非常大 并且 数据非常杂乱
稳定性:不稳定

选择排序

基本思想:
每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完 。

1. 直接选择排序

在元素集合 a[0]–a[n-1] 中选出选出最小的排在最前面,最大的排在最后面。在剩余的元素集合 a[1]–a[n-2] 中再分别选出次最大、最小,重复操作到只剩1个元素或没有元素了。
在这里插入图片描述

代码:

void SelectSort(int* a, int left, int right){
	//[begin,end],待选择最小,最大值的数组范围
	int begin = left,end = right - 1;
	while (begin < end){
		int max = begin;
		int min = begin;
		//1、选出最大的、最小的
		//tip:更新以后选择次大,次小的,范围要随着end,begin变化
		for (int i = begin; i <= end; i++){
			if (a[max] < a[i]){
				max = i;
			}
			if (a[min]>a[i]){
				min = i;
			}
		}
		//2、最小的排在最前面,最大的排在最后面
		swap(&a[min],&a[begin]);
		//如果max在begin,此时begin中的元素已交换到a[min],更新max
		if (max == begin){
			max = min;
		}
		swap(&a[max],&a[end]);
		//3、更新begin,end,即,循环选出次大的,次小的
		++begin;
		--end;
	}
}

算法分析:

平均时间复杂度:O(N^2),双层循环
空间复杂度:O(1),创建了begin,end,max,min临时变量
应用场景:数据量非常大 并且 数据非常杂乱
稳定性:不稳定。(7) 2 5 9 3 4 [7] 1 当我们利用直接选择排序算法进行排序时候,(7)和1调换,(7)就跑到了[7]的后面了,原来的次序改变了,这样就不稳定了。
缺陷:直接选择排序会将前面比较过的元素重复比较

2. 堆排序

建小堆,将根节点元素与最后1个节点的元素交换,再对剩下的元素进行堆向下调整。重复操作至只剩下一个元素,得到降序序列。解决了直接排序的元素重复比较问题。
在这里插入图片描述

代码:

void HeapSort(int* a, int n){//堆排序
	//1、建小堆,从最后1个非叶子节点开始
	for (int i = n / 2 - 1; i >= 0; i--){
		AdjustDown(a,n,i);
	}
	//2、堆排序
	//end为数组元素个数
	int end = n ;
	//调整到只剩一个元素
	while (end>1){
		//交换队尾和队头元素
		swap(&a[0], &a[--end]);
		//从队头开始向下调整end个元素
		AdjustDown(a,end,0);
	}
}

算法分析:

建堆平均时间复杂度:O( N )
堆排序平均时间复杂度:O( N logN )
空间复杂度:O(1)
稳定性:不稳定。因为交换时是隔着元素进行交换。

交换排序

基本思想:
所谓交换,就是根据序列中两个记录键值的比较结果来对换这两个记录在序列中的位置

1. 冒泡排序

比较 n-1 圈,每一圈将更大值往后交换,最后将元素集合 a[0]–a[n-1] 中的最大值交换到最后一个元素处
在这里插入图片描述

代码:

void BubbleSort(int * a, int n){
	assert(a);
	int end = n;
	    //1、比较n-1圈
	while (end-1){
		//设置一个标志位,若每回比较时有交换数据,则flag=1,表示当前数据序列仍无序;若flag==0,则当前序列已经有序。
		int flag = 0;
		int i = 0;
		//2、每圈比较end-1次,将更大值往后交换。最大值交换到最后1个元素处
		for (i = 1; i<end; i++){
			if (a[i - 1]>a[i]){
				swap(&a[i - 1], &a[i]);
				flag = 1;
			}
		}
		if (0 == flag){
			break;
		}
		end--;
	}
}

算法分析:

平均时间复杂度:O( N^2 ),嵌套双循环
最好时间复杂度:O( N ),元素有序,只循环1次退出
空间复杂度:O(1)
稳定性:稳定。

2. 快速排序

任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于等于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。
在这里插入图片描述

快速排序框架:

void quikSort(int* arry, int left, int right){
	if (right - left <= 1){
		return;
	}
	//a[flag]为基准值
	int flag = Partion(arry, left, right);
	//元素均小于等于基准值的左子序列
	quikSort(arry, left, flag);
	//元素均大于基准值的右子序列
	quikSort(arry, flag + 1, right);
}

算法分析:

单个分割算法时间复杂度:O( N )
快速排序平均时间复杂度:O( NlogN )
稳定性:不稳定。元素间间隔着距离交换

1. hoare法

分割前选最左侧元组作为基准值key,end从后往前找,找到小于等于key的值;begin从前往后找大于key的值。交换begin和end位置的元素。重复以上操作直到begin和end相遇,在该位置放入基准值的值。
在这里插入图片描述

tip:
a[left]为基准值时,要先从右往左找,这样在最后一次查找时指针相遇的位置为比key小的值,end若未找到比key小的值而与begin相遇,这里的值小于基准值,可以交换a[left],a[begin];若end找到比key小的值,而begin没有找到比key大的值而相遇,此时,这里的值还是小于key,可以交换a[left],a[begin]。若先从左往右找,则最后一次相遇位置的值可能大于基准值a[left],不能交换
*/

代码:

//hoare版本,返回之类型为int,返回基准值的下标
int Partion1(int* a, int left, int right){
	assert(a);
	int begin = left;
	int end = right - 1;
	int key = a[begin];//基准值,从{a[begin],a[end]}中选一个
	while (begin<end){
		//1、从右往左找,找到小于等于key的值
		while (begin<end && a[end]>key){
			end--;
		}
		//2、从左往右找,找到大于key的值
		while (begin<end && a[begin] <= key){
			begin++;
		}
		//3、若此时位置的值不等于基准值,则交换值
		if (begin<end){
			swap(&a[begin], &a[end]);
		}
	}
	//4、将基准值放入刚才的位置
	if (key != a[begin]){
		swap(&a[left], &a[begin]);
	}
	//5、返回基准值的新位置
	return begin;
}

2. 挖坑法

a[begin]为基准值key时,end从后往前找大于等于key的值,找到就停下,将该元素放入begin,end此时成为新的坑位。begin从前往后找小于key的值,找到就停下,将该元素放入end,begin此时成为新的坑位。重复以上操作直至begin与end相遇,该位置放入基准值key。
在这里插入图片描述

代码:

//挖坑法
int Partion2(int* a, int left, int right){
	assert(a);
	int begin = left;
	int end = right - 1;
	int key = a[begin];
	while (begin<end){
		//1、从后往前找小于等于key的值,放入a[begin],a[end]为新的坑位
		while (begin<end && a[end] > key){
			end--;
		}
		if (begin<end){
			a[begin] = a[end];
			begin++;
		}
		//2、从前往后找大于key的值,放入a[end],a[begin]成为新的坑位
		while (begin<end && a[begin]<=key){
			begin++;
		}
		if (begin<end){
			a[end] = a[begin];
			end++;
		}
	}
	//3、用基准值填最后end(或begin)的位置的值
	a[begin] = key;
	//4、返回基准值的位置
	return begin;
}

3. 前后指针法

以a[right-1]作为基准值key,设置前后指针prev,cur,cur在前,prev在后。当cur、prev一前一后时,表明cur,prev所指向的元素都小于等于基准值;若cur,prev之间有距离,(prev,cur)中保存的都是大于基准值的值;若a[cur]<=key并且cur,prev之间有距离时,++prev,交换a[prev],a[cur],使(prev,cur)中保存的都是大于基准值的值。
在这里插入图片描述

代码:

int Partion3(int* a, int left, int right){
	int cur = left;
	int prev = cur - 1;
	int key = a[right - 1];
	while (cur<right){
		//prev与cur之间保存大于基准值的值
		if (a[cur] <= key && ++prev != cur){
			swap(&a[prev], &a[cur]);
		}
		cur++;
	}
	/*不需要这一步,因为在最后一次交换中,若cur,prev之间有距离,则a[right-1]已经被交换到++prev处;若cur,prev之间没有距离,则prev直接标记a[right-1]。
	if(++prev!=right-1){
	swap(&a[prev],&a[right-1]);
	}*/
	//返回基准值的位置
	return prev;
}

归并排序

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

划分子序列到每个子序列最多有1个元素,采用递归处理左侧,递归处理右侧的方法。将左侧与右侧子序列合并,按升序放入temp。在合并之后及时将temp中的元素放回原数组a.
在这里插入图片描述

代码:

//左闭右开区间,[left,right)
void MergeSort(int* a, int left, int right, int* temp){
	//递归出口:当至多有1个元素时,返回
	if (right -left<= 1){
		return;
	}
	int mid = left + ((right- left) >> 1);
	//2、递归处理左侧,递归处理右侧,此时[left,mid),[mid,right),mid在左侧无法取到,在右侧可以取到
	MergeSort(a,left,mid,temp);
	MergeSort(a,mid,right,temp);
	//3、合并,[begin1,end1),[begin2,end2)
	int begin1 = left,end1=mid;
	int begin2 = mid,end2=right;
	int index = left;
	//a.将左侧与右侧合并,按升序放入temp
	while (begin1 < end1 && begin2 < end2){
		if (a[begin1] <= a[begin2]){
			temp[index++] = a[begin1++];
		}
		else{
			temp[index++] = a[begin2++];
		}
	}
	//b.将左侧或右侧中剩下的元素放入temp
	while (begin1 < end1){
		temp[index++] = a[begin1++];
	}
	while (begin2 < end2){
		temp[index++] = a[begin2++];
	}
	//4、将temp中的元素及时放回数组a,[left,right),(right-left)为right与left之间的有效元素个数
	memcpy(a+left,temp+left,(right-left)*sizeof(int));
}

算法分析:

平均时间复杂度:O( NlogN )
空间复杂度:O(N),需要辅助空间临时存储元素
稳定性:稳定。

代码链接:

点击获取源码。

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java常见排序算法有以下几种: 1. 冒泡排序(Bubble Sort):比较相邻的元素,如果顺序错误就交换位置,重复这个过程直到整个数组有序。 2. 选择排序(Selection Sort):在未排序序列中找到最小(大)元素,将其放到已排序序列的末尾,重复这个过程直到整个数组有序。 3. 插入排序(Insertion Sort):将未排序的元素插入已排序序列中的合适位置,重复这个过程直到整个数组有序。 4. 快速排序(Quick Sort):选择一个基准元素,将数组分成两部分,左边部分的元素都小于基准元素,右边部分的元素都大于基准元素,对两部分分别进行快速排序,重复这个过程直到整个数组有序。 5. 归并排序(Merge Sort):将数组分成两半,分别对每一半进行归并排序,然后将两个有序的子数组合并成一个有序的数组,重复这个过程直到整个数组有序。 6. 堆排序(Heap Sort):首先将数组构建成一个最大堆(或最小堆),然后将堆顶元素与最后一个元素交换位置并移出堆,再调整堆使其重新满足堆的性质,重复这个过程直到整个数组有序。 7. 希尔排序(Shell Sort):将数组按照一定间隔分组,对每一组进行插入排序,然后缩小间隔再次分组并进行插入排序,重复这个过程直到间隔为1,即最后一次对整个数组进行插入排序。 8. 计数排序(Counting Sort):统计数组中每个元素出现的次数,然后根据统计信息将元素放回原数组中,重复这个过程直到整个数组有序。 9. 桶排序(Bucket Sort):将数组划分为若干个桶,将元素放入对应的桶中,然后对每个桶中的元素进行排序,最后将所有非空桶按照顺序合并起来。 10. 基数排序(Radix Sort):将元素按照位数进行排序,先按个位数字进行排序,然后按十位数字进行排序,依次类推,直到最高位。 以上是Java常见排序算法,每种算法在不同的情况下有不同的适用性和性能表现。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值