数据结构与算法-排序

一、基本含义

参考:https://blog.csdn.net/hellozhxy/article/details/79911867

1.1 含义

含义:对一序列对象根据某个关键字进行排序。
稳定性
说明:1、a = b。2、排序前,a在b前面。
——稳定:排序后,a仍然在b的前面,则稳定。
——不稳定:排序后,a可能会出现在b的后面,则不稳定。
只有当在“二次”排序时不想破坏原先次序,稳定性才有意义。如按人的年龄排序时,人还有身高等属性。
内排序:所有排序操作都在内存中完成。
外排序:由于数据太大,因此把数据放在磁盘中,而排序通过磁盘和内存的数据传输才能进行。
性能
——时间复杂度: 一个算法执行所耗费的时间。
——空间复杂度:运行完一个程序所需内存的大小。

1.2 分类

在这里插入图片描述
在这里插入图片描述
n: 数据规模
k: “桶”的个数
In-place: 占用常数内存,不占用额外内存
Out-place: 占用额外内存

1.3 比较和非比较

1.3.1 比较排序

含义:每个数都必须和其他数进行比较,才能确定自己的位置。
优势:适用于各种规模的数据,不在乎数据的分布,都能进行排序。比较排序适用于一切需要排序的情况。
实例:常见的快速排序、归并排序、堆排序、冒泡排序等属于比较排序。

1.3.2 非比较排序

含义:非比较排序是通过确定每个元素之前,应该有多少个元素来排序。只要确定每个元素之前的已有的元素个数即可,所有一次遍历即可解决。
优势:时间复杂度底,但由于非比较排序需要占用空间来确定唯一位置。所以对数据规模和数据分布有一定的要求。
实例:计数排序、基数排序、桶排序。

二、实例

2.1 插入排序(Insertion Sort)

本质:将一个数据插入到已经排好序的有序数据中。

2.1.1 思路

1、将一个元素插入到已有序的数组中,在初始时未知是否存在有序的数据,因此将元素第一个元素看成是有序的。
2、与有序的数组进行比较,比它大则直接放入,比它小则移动数组元素的位置,找到个合适的位置插入
3、当只有一个数时,则不需要插入了,因此需要n-1趟排序,比如10个数,需要9趟排序

2.1.2 代码实现

外层for循环控制需要排序的趟数,内层for循环控制找到合适的插入位置(并且插入的位置不能小于0)

public static void insertionSort(int[] arr){
    if(arr.length == 0){
		return arr;
	}
    for (int i = 1; i < arr.length; i++){    	
        for (int j = i; j > 0 && arr[j] < arr[j-1]; j--){
            int temp = arr[j];
			arr[j] = arr[j-1];
        	arr[j-1] = temp;
        }   
    }
    return arr;
}

2.2 希尔排序(Shell Sort)

含义:也叫缩小增量排序。
本质:插入排序的升级版。把原来比较步长1改成了变量。

2.2.1 思路

1、根据当前步长,采用插入法排序间隔比较元素。
2、重复1。

2.2.2 代码实现

public static int[] shellSort(int[] arr){
    if(arr.length == 0){
		return arr;
	}
	// 设置比较步长
	for(int gap = arr.length/2; gap >= 1; gap = gap /2){
		// 遍历。
    	for (int i = gap; i < arr.length; i++){    
    		// 比较。	
        	for (int j = i - gap; j >= 0 && arr[j] < arr[j + gap]; j = j - gap){
            	int temp = arr[j];
				arr[j] = arr[j + gap];
        		arr[j + gap] = temp;
        	}
        }   
    }
    return arr;
}

2.3 选择排序(Selection Sort)

2.3.1 思路

1、找到数组中最大的元素,与数组最后一位元素交换。
2、当只有一个数时,则不需要选择了,因此需要n-1趟排序,比如10个数,需要9趟排序

2.3.2 代码实现

两个for循环,外层循环控制排序的趟数,内层循环找到当前趟数的最大值,随后与当前趟数组最后的一位元素交换

public static int[] selectionSort(int[] arr){
    for (int i = 0; i < arr.length; i++){
		int minIndex = i;
		// 找到后面最小的数
        for (int j = i; j < arr.length; j++){
            if (arr[j] < arr[minIndex]){
           		minIndex = j;            
            }
        }
        // 把后面最小的数拿到当前位置。
        int temp = arr[minIndex];
        arr[minIndex] = arr[i];
        arr[i] = temp;
    }
    return arr;
}

2.4 堆排序(Heap Sort)

2.4.1 含义

:具有n个元素的序列(k1,k2,…,kn),当且仅当满足
在这里插入图片描述
时称之为堆。
堆特点
1、堆顶元素(即第一个元素)必为最小项(小顶堆)。
2、若以一维数组存储一个堆,则堆对应一棵完全二叉树,且所有非叶结点的值均不大于(或不小于)其子女的值,根结点(堆顶元素)的值是最小(或最大)的。即父节点要么最大,要么最小。
3、完全二叉树有个特性:左边子节点位置 = 当前父节点的两倍 + 1,右边子节点位置 = 当前父节点的两倍 + 2
在这里插入图片描述
在这里插入图片描述
堆排序
堆排序是一种树形选择排序,是对直接选择排序的有效改进。

2.4.2 思路

1、父节点arr[i],则左子节点为arr[2i+1],右子节点为arr[2i+1]
2、比较当前父节点是否大于子节点,如果大于就交换,直到一趟建堆完成

2.4.3 代码实现

public static void heapSort(int[] arr,int currentRootNode, int size){
	// 左子树和右子树的位置
	int left = 2 * currentRootNode + 1;
	int right = 2 * currentRootNode + 2;
	int max = currentRootNode; // 假设根元素最大。
    if(right < size){
    	//如果比当前根元素要大,记录它的位置		
		max = arrays[max] > arrays[left] ? max : left;
		max = arrays[max] > arrays[right] ? max : right;
		//如果最大的不是根元素位置,那么就交换
		if(max != currentRootNode){
			int temp = arrays[max];
			arrays[max] = arrays[currentRootNode]; 
			arrays[currentRootNode] = temp; 
			//继续比较,直到完成一次建堆
			heapSort(arr,max,arr.length);
		}
	}   
}
public static void heapSort(int[] arr){
	for(int i = 0; i < arr.length; i++){
		heapSort(arr, i, arr.length)
	}
}

2.5 冒泡排序(Bubble Sort)

2.5.1 思路

1、俩俩交换,大的放在后面,第一次排序后最大值已在数组末尾。
2、因为俩俩交换,需要n-1趟排序,比如10个数,需要9趟排序。

2.5.2 代码实现

1、两个for循环,外层循环控制排序的趟数,内层循环控制比较的次数
2、每趟过后,比较的次数都应该要减1。
3、如果一趟排序后也没有交换位置,那么该数组已有序。

// 
public static int[] bubbleSort(int[] arr){
    if(arr.length == 0){
		return arr;
	}
	// 装载临时变量
	int temp;
	// 外层循环是排序的趟数
    for (int i = 0; i < arr.length; i++){
    	//内层循环是当前趟数需要比较的次数。
        for (int j = 0; j < arr.length - 1 - i; j++){
        	//前一位与后一位比较,若大,则交换。
            if (arr[j+1] < arr[j]){
                temp = arr[j];
                arr[j] = arr[j+1];
                arr[j+1] = temp;
            }
        }
    }
}
// 优化。当该趟比较完之后,没有发生交换,说明已经排好序了,不再需要重新比较了。
public static int[] bubbleSort2(int[] arr){
	int temp;	
	int isChange;// 记录是否发生了置换,0没有、1发生。
    for (int i = 0; i < arr.length; i++){
    	isChange = 0;
        for (int j = 0; j < arr.length - 1 - i; j++){
            if (arr[j+1] < arr[j]){
                temp = arr[j];
                arr[j] = arr[j+1];
                arr[j+1] = temp;
                isChange = 1;
            }            
        }
        // 0没有发生了置换,说明已经排好序了。
        if (isChange == 0) {
        	break;
        }
    }
}

2.6 快速排序(Quick Sort)

1、在数组中找一个元素(节点),比它小的放在节点的左边,比它大的放在节点右边。一趟下来,比节点小的在左边,比节点大的在右边。
2、不断执行这个操作….

2.6.1 思路

1、以当前元素为参考,比它大(小)放左边,比它小(大)放右边。
2、递归调用,直到数组长度为1。

2.6.2 代码实现

1、快速排序用递归比较好写。
2、参考点取中间,使用L和R表示数组的最小和最大位置。
3、不断进行比较,直到找到比参考点小(大)的数,随后交换,不断减小范围。
4、递归L到支点前一个元素(j),找最大(小)值的索引。
5、递归支点后一个元素(i)到R元素,找最小(大)值的索引。
6、若找到,则交换位置。

// 从小到大排序。
public static int[] quickSort(int[] arr,int L,int R){
	int i = L,j = R;// 第一个和最后一个元素的索引。    
    int pivot = arr[(L + R) / 2]; // 参考点
	// 扫描参考点两端
    while (i < j) {
    	//左找大
        while (pivot > arr[i]){
			i++;
		}      	
        //右找小
        while (pivot < arr[j]){
			j--;
		}            
        // 若找到,则左右交换。
        if (i < j) {
           int temp = arr[i];
           arr[i] = arr[j];
           arr[j] = temp;
           // 目的,从下一个开始。
           i++;
           j--;
		}
	}
	//第一趟排序完成,保证了左小右大。
	//“左边”再做排序,直到左边剩下一个数(递归出口)
	if (L < j){
		quickSort(arr, L, j);
	}
	//“右边”再做排序,直到右边剩下一个数(递归出口)
	if (i < R){
		quickSort(arr, i, R);
	}
}

2.7 归并排序(Merge Sort)

基本思想:将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。
将两个已排好序的数组合并成一个有序的数组,称之为归并排序

2.7.1 思路

1、遍历两个数组,比较它们的值。谁比较小,谁先放入大数组中,直到数组遍历完成。

2.7.2 代码实现

public static int[] mergeSort(int[] arr) {
	if (arr.length < 2) {
		return arr;
	} else {        
        int M = arr.length / 2;     
        int[] leftArray = Arrays.copyOfRange(arr,0,M); 
        int[] rightArray = Arrays.copyOfRange(arr,M,arr.length);
        return merge(mergeSort(leftArray),mergeSort(rightArray));
    }
}
// 合并数组
public static int[] merge(int[] left, int[] right) {        
	int[] result = new int[left.length + right.length];    
    for (int index = 0; i = 0,j = 0; index < result.length; index++) {
    	// i >= left.length说明左边数组元素,已经全部放入大数组中了。
    	// left[i] >= right[j]说明从小到大的顺序排列。
    	if(i >= left.length || left[i] >= right[j]){
			result[index] = right[j++];
		}else{
			result[index] = left[i++];
		}
    }
    return result;
}

2.8 桶排序/基数排序(Radix Sort)

2.8.1 含义

含义:基数排序(radix sort)属于"分配式排序"(distribution sort),又称"桶子法"(bucket sort)或bin sort,顾名思义,它是透过键值的部份资讯,将要排序的元素分配至某些"桶"中,藉以达到排序的作用,基数排序法是属于稳定性的排序,其时间复杂度为O (nlog®m),其中r为所采取的基数,而m为堆数,在某些时候,基数排序法的效率高于其它的稳定性排序法。

2.8.2 思路

1、根据数组长度(行)和10个数字(列)创建二维数组。
2、初始化二维数组,数组元素的索引确定其行位置,元素个位上的数字确定列位置。
3、回收数组元素(即重新赋值),先回收行值再回收列值。
4、第一趟排序,数组元素的索引确定其行位置,元素十位上的数字(若无则为0)确定列位置。回收元素。
5、重复2、3、4步骤。

2.8.3 代码实现

 public static void radixSort(int[] arr) {
	int max = findMax(arr, 0, arr.length - 1);
    //需要遍历的次数由数组最大值的位数来决定
    for (int i = 1; max / i > 0; i = i * 10) {
        int[][] buckets = new int[arr.length][10];
        //获取每一位数字(个、十、百、千位...分配到桶子里)
        for (int j = 0; j < arr.length; j++) {
           int num = (arrays[j] / i) % 10;          
           buckets[j][num] = arr[j];//将其放入桶子里
        }
        //回收桶子里的元素
        int k = 0;
        //有10个桶子,原因只有十个数字
        for (int j = 0; j < 10; j++) {
           //对每个桶子里的元素进行回收
           for (int l = 0; l < arr.length ; l++) {
               //若桶子里面有元素就回收(数据初始化会为0)
               if (buckets[l][j] != 0) {
                   arr[k++] = buckets[l][j];
                }
            }
        }
	}
}
// 找最大值。
public static int findMax(int[] arr, int L, int R) {
	int max = arr[L];
	//如果该数组只有一个数,那么最大的就是该数组第一个值了
    if (L != R) {
        int b = findMax(arr, L + 1, R);//找出整体的最大值
        max = max > b ? max : b;
    }
    return max;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值