排序的基本概念
排序的定义
排序就是将原本无序的序列重新排列成有序的序列
内外部排序
内部排序,排序列完全存放在内存中所进行的排序过程,整个过程只是在内存中完成。
外部排序,指大文件的排序,即待排序的记录存储在外存,待排序的文件无法一次装入内存,需要在内存和外部存储器之间进行多次数据交换,才能完成排序。
原地排序和非原地排序
原地排序,排序过程不申请多余的存储空间
非原地排序相反,需要申请额外的空间来辅助
排序的稳定性
或者:对于待排序每个元素,假设a=b,排序前a在b前面,那么排序后a也在b前面,称之为稳定的,否则不稳定的。
排序的分类
内部排序
插入排序
(1)直接插入排序
算法思想:每趟将一个待排序的关键字按照其值的大小插入到已经排好的部分有序序列的适当位置上,直到所有待排关键字都被插入到有序序列中为止。
public class Solution {
static void insertSort(int arr[]) {
for (int i = 1; i <= arr.length - 1; i++) {
int temp = arr[i];
int j = i - 1;
for (; j >= 0; j--) {
if (temp < arr[j])
arr[j + 1] = arr[j];
else
break;
}
arr[j + 1] = temp;
}
}
public static void main(String[] args) {
int arr[] = {12, 36, 56, 1, 78, 45, 36};
insertSort(arr);
System.out.println(Arrays.toString(arr));
}
}
(2)折半插入排序
public class Solution {
static void zBInsertSort(int arr[]){
for (int i=1;i<arr.length;i++){
int temp =arr[i];
int low =0;
int high =i-1;
while(low<=high){
int mid =(high+low)/2;
if (arr[mid]<=arr[i])
low =mid+1;
else
high =mid-1;
}
for (int j=i-1;j>high;j--)
arr[j+1] = arr[j];
arr[high+1]= temp;
}
}
public static void main(String[] args) {
int arr[] = {12, 36, 56, 1, 78, 45, 36};
zBInsertSort(arr);
System.out.println(Arrays.toString(arr));
}
}
参考:
排序----折半插入排序_被Python玩的Kenny的博客-CSDN博客_折半插入
总结:
直接插入
最坏时间复杂度:O(n^2)
如果序列是倒序的,则排第n个元素时,需要与前n-1个元素进行比较,前n-1个元素也都要后移。这样n从1取到n就是,比较和移动的次数都是0+(2-1)+(3-1)+..+(n-1)结果就是n*(n-1)/2,所以是O(n2)级别。
最好时间复杂度为:O(n) 比较次数都为1,都移动一次
空间复杂度为:0(1)
是一个稳定的算法,适用于顺序存储和链式存储。
折半插入
时间复杂度:它只是利用了折半查找减少了关键字的比较次数,而记录的移动次数不变!其时间复杂度为O(n*n)
比较次数为 :O(n logn); 与待排序表的初始状态无关,仅取决于表中的元素个数n,而元素的移动次数并未改变,它依赖于排序表的初始状态。
只适用于顺序存储。
(3)希尔排序(缩小增量排序)
算法思想:将待排序列按增量的选取规则分为几个子序列,分别对这几个子序列进行直接插入排序。然后继续将上一次选取出来的增量按照规则继续选取增量,并将上一次排序后的序列继续分组,分成几个子序列后继续进行直接插入排序,这样循环,直至增量选取为1时停止。此时序列大致有序,只需简单调整即可。
public class ShellSort {
public static void main(String[] args) {
//定义数组
int[] arr = {99, 55, 2, 3, 9, 10, 22, 34, 67, 89, 69, 92, 101, 102};
//增量
int gap = arr.length;
//排序
sort(arr, gap);
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
}
public static void sort(int[] arr, int gap) {
//确定新一轮分组的增量
gap = gap / 2;
//对数组进行分组
for (int i = 0; i < gap; i++) {
for (int j = i + gap; j < arr.length; j += gap) {
//获取当前元素,然后在本组内部向前比较并排序
int current = arr[j];
for (int k = j - gap; k >= i; k -= gap) {
if (arr[k] > current) {
//插入
arr[k + gap] = arr[k];
arr[k] = current;
}
}
}
}
if (gap > 1) {
sort(arr, gap);
}
}
}
时间复杂度:(和增量有关系)【不是说我们这个代码的时间复杂度是这个】在一定范围之内它是O(n^1.3~n^1.5)
空间复杂度:O(1)
稳定性:不稳定(在比较过程中发生了跳跃式的交换,那么就是不稳定的)
选择排序
(1)简单选择排序
算法思想:每一次从无序区间选出最大(或最小)的一个元素,存放在无序区间的最后(或最前),直到全部待排序的数据元素排完 。
过程分析:
时间复杂度:O(n²)
public class Solution {
static void selectSort(int arr[]){
for (int i=0;i<arr.length-1;i++)
{
int index=i;
int temp =arr[i];
for (int j=i+1;j<=arr.length-1;j++){
if (arr[index]>arr[j])
index =j;
}
arr[i] =arr[index];
arr[index] =temp;
}
}
public static void main(String[] args) {
int arr[] = {12, 36, 56, 1, 78, 45, 100};
selectSort(arr);
System.out.println(Arrays.toString(arr));
}
}
(2)堆排序
算法思想:首先将关键字按从上到下从左到右的顺序来建一棵完全二叉树,然后将该完全二叉树调整为大顶堆(或小顶堆),每次调整完之后取下根节点,然后取最下层最右边的结点填充过去,继续调整为大顶堆(或小顶堆)。如此循环,直到取完所有关键字,每次取下的根节点按顺序列出即可得到一个有序序列。
大顶堆:每个父节点的值都大于或等于其孩子节点的值。
小顶堆:每个父节点的值都小于或等于其孩子节点的值。
如何将初始无序的完全二叉树调整为大顶堆或小顶堆?
首先找到最后一个非叶节点,从它开始由右至左,由下至上,对每个结点进行调整。具体调整的是将当前非叶节点与其孩子节点作比较,如果存在大于当前父节点的孩子节点,则相交换,直到父节点比它的孩子节点都大,然后按顺序继续比较其他非叶节点。
时间复杂度:O(nlogn)
import java.util.Arrays;
public class HeapSort {
public static void main(String[] args) {
int[] array = {4,6,1,2,9,8,3,5};
heapSort(array);
System.out.println(Arrays.toString(array));
}
/**
* 堆排序
*/
public static void heapSort(int[] arr){
//为什么从arr.length/2-1开始?
for (int i = arr.length/2-1; i >= 0 ; i--) {
adjustHeap(arr,i,arr.length);
}
for (int j = arr.length-1; j > 0; j--) {
int temp = arr[j];
arr[j] = arr[0];
arr[0] = temp;
/*为什么从0开始?
因为在第一次构建大顶堆后让堆顶元素和末尾元素进行交换
而对于其他的非叶子结点所对应的子树都是大顶堆就无需调整,
只需要堆顶元素(下标为0的非叶子结点)的子树调整成大顶堆
*/
adjustHeap(arr,0,j);
}
}
/**
* 构建大顶堆
* 注意:
* 这个方法并不是将整个树调整成大顶堆
* 而是以i对应的非叶子结点的子树调整成大顶堆
* @param arr 待调整的数组
* @param i 非叶子结点在数组中的索引(下标)
* @param length 进行调整的元素的个数,length是在逐渐减少的
*/
public static void adjustHeap (int[] arr,int i,int length){
/*取出当前非叶子结点的值保到临时变量中*/
int temp = arr[i];
/*j=i*2+1表示的是i结点的左子结点*/
for (int j = i * 2 + 1; j < length ; j = j * 2 + 1) {
if (j+1 < length && arr[j] < arr[j+1]){ //左子结点小于右子结点
j++; //j指向右子结点
}
if (arr[j] > temp){ //子节点大于父节点
arr[i] = arr[j]; //把较大的值赋值给父节点
//arr[j] = temp; 这里没必要换
i = j; //让i指向与其换位的子结点 因为
}else{
/*子树已经是大顶堆了*/
break;
}
}
arr[i] = temp;
}
}
交换排序
(1)冒泡排序
思想:首先第一个关键字和第二个关键字比较,如果前者大,则二者交换,否则不交换,然后第二个关键字和第三个关键字比较,一直这样进行下去,最终最大的关键字被交换到了最后,这样一趟冒泡排序完成,经过多趟这样的排序,直到一趟排序中没有发生交换,此时便结束,整个序列有序。
public class Solution {
static void bubbleSort(int arr[]){
for (int i=0;i<arr.length-1;i++){ //需要n-1躺比较
int temp=0;
int flag=1;
for(int j=0;j<arr.length-1-i;j++){
if (arr[j]>arr[j+1]){
temp =arr[j];
arr[j] = arr[j+1];
arr[j+1] =temp;
flag =0;
}
}
if (flag ==1)
break;
}
}
public static void main(String[] args) {
int[] arr = new int[]{12, 36, 56, 1, 78, 45, 316};
bubbleSort(arr);
System.out.println(Arrays.toString(arr));
}
}
时间复杂度:O(n^2) 最好的情况:O(n)【有序的情况】
空间复杂度:O(1)
稳定性:稳定
(2)快速排序
1.原理:
(1)从待排序区间选择一个数,作为基准值 (pivot) ;
(2)Partition: 遍历整个待排序区间,将比基准值小的(可以包含相等的)放到基准值的左边,将比基准值大的(可以包含相等的)放到基准值的右边;
(3)采用分治思想,对左右两个小区间按照同样的方式处理,直到小区间的长度 == 1 ,代表已经有序,或者小区间的长度 == 0 ,代表没有数据
import java.util.Arrays;
public class TestDemo {
public static void quickSort(int[] arr,int left,int right){
if(left >= right){
return;
}
int pivot = partition(arr,left,right);//找到了基准
quickSort(arr,left,pivot-1);
quickSort(arr,pivot+1,right);
}
private static int partition(int[] arr,int start,int end){
int tmp = arr[start];
while(start < end){
while(start < end && arr[end] >= tmp){//防止一直--下去,会有数组越界的问题,注意:此处不能不加等号,否则会出现死循环,如下图所示
end--;
}
arr[start] = arr[end];
while(start < end && arr[start] <= tmp){//防止一直++下去,会有数组越界的问题
start++;
}
arr[end] = arr[start];
}
arr[start] = tmp;
return start;
}
/*public static void test(int capacity){
int[] arr = new int[capacity];
for(int i = 0; i < arr.length; i++){
arr[i] = i;
}
long start = System.currentTimeMillis();
quickSort(arr,0, arr.length - 1);
long end = System.currentTimeMillis();
System.out.println(end - start);
}*/
public static void main(String[] args) {
int[] arr = {5, 1, 2, 4, 3, 6, 9, 7, 10, 8};
quickSort(arr,0,arr.length - 1);
//test(100_0000);
System.out.println(Arrays.toString(arr));
}
}
归并排序(二路归并排序)
算法思想:以原始序列含7个关键字为例,首先将序列看成7个含有一个关键字的子序列,然后两两归并,形成若干个有序二元组,最后一个没有归并对象,单独一个组,之后继续两两归并,形成若干个有序的四元组,直到归并完全部的关键字,形成一个有序七元组。
时间复杂度:O(n * log(n))
空间复杂度:O(n)
稳定性:稳定
import java.util.Arrays;
public class TestDemo {
public static int[] arrayMerge(int[] arr1, int[] arr2){
int[] arr = new int[arr1.length + arr2.length];
int s1 = 0;
int e1 = arr1.length-1;
int s2 = 0;
int e2 = arr2.length-1;
int i = 0;
while(s1 <= e1 && s2 <= e2){
if(arr1[s1] <= arr2[s2]){
arr[i++] = arr1[s1++];
}else{
arr[i++] = arr2[s2++];
}
}
while(s1 <= e1){
arr[i++] = arr1[s1++];
}
while(s2 <= e2){
arr[i++] = arr2[s2++];
}
return arr;
}
public static void main(String[] args) {
int[] arr1 = {0, 2, 4, 6};
int[] arr2 = {1, 3, 5, 7};
int[] ret = arrayMerge(arr1, arr2);
System.out.println(Arrays.toString(ret));
}
}
基数排序
算法思想:若分配的是数字,数字范围是0~9,所以需假设有10个桶来放关键字,每个桶分别代表0,1,2,3 、、、9,然后所有数字要位数相同,不够用0来补(如12,补为012),以三位数为例(原始序列中,位数最长的关键字是三位数),需要进行三次分配,第一次按个位分配,个位是几就放入几号桶,全部放完之后,从0号桶开始按顺序取出桶内的数,注意如果一个桶内放了多个关键字,则要按先进先出的原则取出桶内的关键字。第二次按十位分配,原理同上,第三次按百位分配,最后按原则取出关键字后即可得到一个有序序列。
总结
参考
————————————————
https://blog.csdn.net/AlanTuring_hst/article/details/119781346