文章目录
一、基本含义
参考: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;
}