排序算法的思想
什么是排序算法的稳定性?
指两个相同的元素,在多次排序过程中,排序先后的相对位置是否会发生变化。主要用在排序时有多个排序规则的情况。【一般情况下认为,如果两个元素进行相邻的交换,可以认为该算法是稳定的】
1.选择排序
思想:nums[i],遍历数组,第一次从数组中找到最小的元素,与数组第一个元素进行交换;第二次遍历数组查找第二小元素,与数组第二个元素进行交换;总之,每次选择未排序最小元素与未排序元素的首部进行交换
稳定性:由于每次选择的最小元素都与未排序的首部元素进行交换,跨距离交换,有可能会破坏元素之间的相对位置,因此不稳定
时间复杂度:n个元素;要查找n次最小值进行交换,o(n*2)
public void sort(T[] nums) {
//1.求出元素大小
int N=nums.length;
//2.循环遍历
for(int i=0;i<N-1;i++) {
//2.1定义最小元素的索引
int min=i;
//2.2 开始遍历查找最小元素
for(int j=i+1;j<N;j++) {
if(less(nums[j],nums[min])) {
min=j;
}
}
swap(nums,i,min);
}
}
2.冒泡排序
每次比较相邻逆序的元素,进行交换,第一轮结束后最大值会在最右侧;相邻排序;稳定算法
在第一轮循环中,如果没有排序没说明数组已经是有序的(优化冒泡排序)
平均:o(n*2)
最好:N-1次比较,0次交换
public void sort(T[] nums) {
int N=nums.length;
boolean hasSorted=false;
// 每次都把最大元素移到最后一位未排序元素,有N个元素需要排序;
for(int i=N-1;i>0 && !hasSorted;i--) {
hasSorted=true;
for(int j=0;j<i;j++) {
if(less(nums[j+1],nums[j])) {
hasSorted=false;
swap(nums,j,j+1);
}
}
}
3.插入排序
每次都把新元素插入到左侧已经排序好的数组中,插入排序的次数与数组中相邻逆序数组个数有关;每次调整一组逆序数组,每次都是相邻元素交换,具有稳定性
复杂度:最好:正向有序,只需要比较N次,不需要移动
最差:逆向有序,每一个元素比较n次,有n个元素o(n*2)
平均:o(n^2)
public void sort(T[] nums) {
int N=nums.length;
for(int i=1;i<N;i++) {
for(int j=i;j>0 && less(nums[j],nums[j-1]);j--) {
swap(nums,j-1,j);
}
}
4.希尔排序
改进的插入排序;交换不相邻的元素,使逆序数量减少次数大于1;使用间隔h进行交换;不断减小h,最后令h=1
public void sort(T[] nums) {
int N=nums.length;
int h=1;
while(h<N/3) {
h= 3*h+1;
}
while(h>0) {
for(int i=h;i<N;i++) {
for(int j=i;j>= h && less(nums[j],nums[j-h]);j-=h) {
swap(nums,j-h,j);
}
}
h = h/3;
}
}
5.快速排序
选择切分元素;使该元素左边的元素都小于它,右边的元素都大于他,然后开始递归排序左边、右边
类似于二分法思想,因此时间复杂度o(nlogn)
性能分析:
快排是原地排序,不需要辅助数组,但是递归调用需要辅助栈
最好的情况是每次都能将数组对半分,这样递归调用次数最少,复杂度O(NlogN)
最坏情况:第一次从最小的元素开始切分,第二次从第二小;需要比较N^2/2次;
为了防止数组一开始就是有序的,可以先将数组打乱
public class QuickSort {
public void sort(T[] nums) {
shuffle(nums);
sort(nums,0,nums.length-1);
}
private void sort(T[] nums, int l, int h) {
while(h<=l) {
return;
}
//j表示第一次切分元素之后,数组中的第j大元素;左边的数都小于他;右边都大于他
int j=partition(nums,l,h);
sort(nums,l,j-1);
sort(nums,j+1,h);
}
//一次快速排序
private int partition(T[] nums, int l, int h) {
int i=l,j=h+1;
T v=nums[l];//切分元素
while(true) {
while(less(nums[++i],v) && i!=h);
while(less(v,nums[--j]) && j!=l);
//此时,i<j;且nums[i]>v;nums[j]<v;进行交换
if(i>=j) {
break;
}
swap(nums,i,j);
}
swap(nums,l,j);
return j;
}
private void shuffle(T[] nums) {
List<Comparable> list=Arrays.asList(nums);
Collections.shuffle(list);
list.toArray(nums);
}
/*基于切分的快速选择算法 找到排序后数组中的第K个元素 nums[k] */
public T select(T[] nums,int k) {
int l=0,h=nums.length-1;
while(h>l) {
int j=partition(nums, l, h);
if(j==k) {
return nums[k];
}else if(j>k) {
h=j-1;
}else {
l=j+1;
}
}
return nums[k];
}
}
6.归并排序
将数组分成两部分,分别排序,然后将两个有序的数组进行归并
元素融合过程中;注意索引越界
protected T[] aux;
protected void merge(T[] nums,int l,int m,int h ) {
// i 表示左边数组要归并的元素索引 j 表示右边
int i=l,j=m+1;
// 复制数组到辅助栈
for(int k=l;k<=h;k++) {
aux[k]=nums[k];
}
// 开始归并
for(int k=l;k<=h;k++) {
//当左边元素索引大于中间元素索引,表示左边元素全部已经排好序在数组中
if(i>m) {
nums[k]=aux[j++];
}else if(j>h) {
nums[k]=aux[i++];
}else if(aux[i].compareTo(nums[j]) <= 0 ) {
nums[k]=aux[i++];
}else {
nums[k]=aux[j++];
}
}
}
@Override
public void sort(T[] nums) {
aux=(T[]) new Comparable[nums.length];
sort(nums,0,nums.length-1);
}
private void sort(T[] nums, int l, int h) {
if(h<=l) {
return;
}
int mid=l+(h-l)/2;
sort(nums,l,mid);
sort(nums,mid+1,h);
merge(nums,l,mid,h);
}
7.堆排序
堆中某个节点的值总是大于等于其子节点的值;并且是一颗完全二叉树
可以用数组来表示, 位置K的节点的父节点k/2-1;子节点 2k+1,2k+2;
数组索引从0开始
- 上浮和下沉都是调节数组元素为堆的操作
上浮:在堆中,当子节点比父节点大,进行上浮操作,如果比更新之后父节点还大,继续上浮 k>0
下沉:在堆中,当子节点比父节点大,就要进行下沉操作,并且选择左右子节点最大的那个进行交换下沉 2k+2<N
插入元素:将新元素放在数组末尾,然后上浮
删除最大元素:从数组顶端删除最大元素,并将数组最后一个元素放到顶端,下沉
堆排序:
构建堆:首先要把一个无序数组构建成一个符合堆结构的数组,可以选择从左到右上浮一个个元素,可以选择下沉,如果一个节点的两个节点已经是堆有序,那么进行下沉操作可以使这个节点为根点的堆有序
因为叶子节点不需要下沉,只需要操作一半的元素。排序时,可以把堆中的第一个元素一直与后面的元素进行交换,然后下沉新元素,重新构造堆
分析:一个堆的高度为logN,因此在堆中插入和删除元素的复杂度都为logN
对于堆排序:由于要对N个节点进行操作,因此复杂度o(NlogN)
原地排序,没有利用额外空间
/**
* 2.堆排序:交换堆顶元素和堆树中最后一个元素;不删除;然后把第一个元素下沉到合适的位置;
* 在交换堆顶元素和堆数组倒数第二的元素...以此类推;最后数组就是一个递增序列
* 最大堆 排序之后 从小到大
* @param array
*/
public void sort(int[] array){
int N=array.length;
int K=N-1;
/**
* array数组第0个元素开始
* 87 69 12 45 46
* 12 45 46 69 87
*/
//1. 首先构建一个堆数组 使用下沉一半元素 从n/2-1下沉元素到合适的位置;叶子节点占了一般半元素;不需要下沉
//父节点:k/2-1 子节点 : 2*k+1 2*k+2
// 0 1 2 3 4 5
for(int i=N/2-1;i>=0;i--){
sink(array,i,N);
}
for(int num:array){
System.out.print(num+" ");
}
System.out.println();
//2 开始进行堆排序 数组
while(K>0){
//交换堆顶元素和堆中要排序的最后一个元素 swap(0 k)
swap(array,0,K--);
// 把第一个元素下沉到合适的位置()
sink(array,0,K+1); length 比索引位置多一位
}
}
/**
* 下沉元素
* @param array
* @param k
* @param length
*/
private void sink(int[] array, int k, int length) {
while( 2*k +1 <length){
int j=2*k +1;
if((j+1)<length && array[j+1]> array[j]){
j++;
}
if(array[k]> array[j]){
break;
}
swap(array,k,j);
k=j;
}
}
private void swap(int[] array, int i, int j){
int temp=array[i];
array[i]=array[j];
array[j]=temp;
}
https://www.cnblogs.com/onepixel/articles/7674659.html