常见的排序算法及其原理

1.直接插入排序

1.1 原理

整个区间被分为: 有序区间和无序区间
每次选择无序区间的第一个元素,在有序区间内找个合适的位置插入

1.2 实现

依次查询

public static void insertSort(int[] array){
    for (int i = 1; i < array.length; i++) {
    	// 有序区间: [0, i) 
    	// 无序区间: [i, array.length)
        int v = array[i];
        int end = i-1;
        while (end >= 0 && v < array[end]){
            array[end+1] = array[end];
            end--;
        }
            array[end+1] = v;
     }
     System.out.println(Arrays.toString(array));
}

二分法查询

	public static void insertBinSort(int[] array){
        for (int i = 1; i < array.length; i++) {
            int v = array[i];
            int right = i;
            int left = 0;
			// 需要考虑稳定性
            while (left < right){
                int mid = (left+right)>>1;
                if(v>=array[mid]){
                    left = mid+1;
                }else {
                    right= mid;
                }
            }
            // 元素搬移
            for (int j = i; j > left; j--) {
                array[j] = array[j-1];
            }
            array[left] = v;
        }
        System.out.println(Arrays.toString(array));
    }
1.3 时间复杂度、空间复杂度、稳定性

时间复杂度:O(n^2)
空间复杂度:O(1)
稳定
插入排序,初始数据越接近有序,时间效率越高

2. 希尔排序

2.1 原理

先选定一个整数(N),把待排序文件中所有数据分成组,每隔N隔数是一组,并对每一组内的数据进行排序,然后通过某个式子重新确定N值,在进行分组,再对每组进行排序,重复上述步骤,当N == 1时,所有数据在组内排好序
1.希尔排序是对直接插入排序的优化
2.当gap>1时都只是为了让这一组数据接近有序,整体而言具有优化的效果

2.2 实现
public static void shellSort(int[] array){
	int gap = array.length;
	while(gap>1){
	// 时间复杂度为 O(N^1.25) ~ O(1.6N^1.25)
		gap = gap/3+1;
		for(int i = gap;i<array.length;i++){
			int v = array[i];
			int end = i-gap;
			while(end >0 && v < array[end]){
                    array[end+gap] = array[end];
                    end--;
            }
			array[end+gap] = v;
		}
	}
	System.out.println(Arrays.toString(array));
}
2.3 时间复杂度、空间复杂度、稳定性

时间复杂度:不确定,gap不同,时间复杂度也不同
空间复杂度:O(1)
不稳定

3. 选择排序

3.1 原理

每一次从无序区间选出最大(最小)的一个元素,存放在无序区间的最后(最前),直到全部待排序的数据元素排完

3.2 实现

一次只找出最大的数

	public static void swap(int[] array,int left,int right){
        int tmp = array[left];
        array[left] = array[right];
        array[right] = tmp;
    }
    public static void selectSort(int[] array){
        for (int i = 0; i < array.length-1; i++) {
            int maxIndex = 0;
            // 找出最大的数
            for (int j = 0; j < array.length-i; j++) {
                if(array[maxIndex] < array[j]){
                    maxIndex = j;
                }
            }
            
            if(maxIndex != array.length-1-i){
                swap(array,maxIndex,array.length-1-i);
            }
        }
        System.out.println(Arrays.toString(array));
    }

一次找出最大数也找出最小数

	public static void selectSortOP(int[] array){
        int begin = 0;
        int end = array.length-1;
        while (begin <= end){
            int min = begin;
            int max = begin;
            int index = begin+1;
            while (index <= end){
                if(array[index] > max){
                    max = index;
                }
                if(array[index] < min){
                    min = index;
                }
                index++;
            }
            // 将最大元素放在最后
            if(max != end){
                swap(array,max,end);
            }
            // 如果最小值刚好放在了最后一个位置,就要及时更新min
            if(min == end){
                min = max;
            }
            if(min != begin){
                swap(array,max,begin);
            }
            begin++;
            end--;
        }
        System.out.println(Arrays.toString(array));
    }
3.3 时间复杂度、空间复杂度、稳定性

时间复杂度:O(N^2)
空间复杂度:O(1)
不稳定

4. 堆排序

4.1 原理

通过堆来选择无序区间的最大的数
排升序建大堆
排降序建小堆

4.2 实现
	public static void shiftDown(int[] array,int parents,int size){
        int child = parents*2+1;
        while (child < size){
            // 找左右孩子中 较大的孩子
            if(child+1 < size && array[child+1] > array[child]){
                child += 1;
            }
            if(array[child] > array[parents]){
                swap(array,child,parents);
                parents = child;
                child = parents*2+1;
            }else {
                break;
            }
        }
    }
    public static void heapSort(int[] array){
        int lastNoLeaf = (array.length-2)>>1;
        for (int i = lastNoLeaf; i >= 0; i--) {
            shiftDown(array,i,array.length);
        }
        int end = array.length-1;
        while (end >= 0){
            swap(array,0,end);
            shiftDown(array,0,end);
            end--;
        }
    }
4.3 时间复杂度、空间复杂度、稳定性

时间复杂度:O(NlogN)
空间复杂度:O(1)
不稳定

5. 快速排序

5.1 原理

从区间中取一个数据当作基准值,按照基准值将区间分为两部分
基准值左边都比基准值小,基准值右边都比基准值大
然后再排左半部分,再排右半部分

5.2 实现
// 1.交换法
	public static int partition(int[] array,int left,int right){
        int key = array[right-1];
        int begin = left;
        int end = right-1;
        while (begin < end){
        // 从前往后找比基准值大的元素
            while (begin<end && key >= array[begin]){
                begin++;
            }
            // 从后往前找比基准值小的元素
            while (begin<end && key <= array[end]){
                end--;
            }
            if(begin < end)
                swap(array,begin,end);
        }
        // 按照基准值划分后,如果基准值刚好在其所在的位置上就不需要交换了
        if(begin != right-1)
            swap(array,right-1,begin);
        return begin;
    }

// 2.挖坑法
	 public static int partition2(int[] array,int left,int right){
        int begin = left;
        int end = right-1;
        int key = array[end];
        while (begin < end){
        // 从前往后找比基准值大的元素
            while (begin < end && array[begin] <= key){
                begin++;
            }
            
            // 将begin位置的元素填到上一个坑里
            if(begin < end)
                array[end] = array[begin];
                
			// 从后往前找比基准值小的元素
            while (begin < end && array[end] >= key){
                end--;
            }
            
            // 将end位置的元素填到上一个坑里
            if(begin < end)
                array[begin] = array[end];
        }
        // 再把最后一个坑用基准值填上
        array[begin] = key;
        return begin;
    }
    
// 3. 前后指针法:cur 和 prev 之间隔这的都是比基准值大的元素
	public static int partition3(int[] arr,int left,int right){
		int cur = left;
		int prev = cur-1;
		int key = arr[right-1];
		while(cur < right){
		    // 找到比基准值小的位置
		    //如果prev和cur之间没有元素,那就说明cur前面没有比基准值大的元素
			if(arr[cur] < key && ++prev != cur){
				swap(arr,cur,prev);
			}
			cur++;
		}
		// 如果prev不在基准值的位置上
		// 那就说明prev后面有比基准值大的元素
		// 所以要将两者交换
		if(++prev != right-1){
			swap(arr,prev,right-1);
		}
		return prev;
	} 
    public static void quickSort(int[] array,int left,int right){
        if(right - left > 1) {
      	  // 说明区间内至少有两个元素
            int part = partition(array, left, right);
            // 递归排基准值左侧
            quickSort(array, left, part);
            // 递归排基准值右侧
            quickSort(array, part + 1, right);
        }
    }
4.3 时间复杂度、空间复杂度、稳定性

时间复杂度:O(n^2) (如果每次基准值取得都是区间的极值)
平均是O(nlogn) (每次都刚好把区间分为两等份)
空间复杂度:O(logn)
稳定性:不稳定

4.4 代码优化
  • 取基准值优化
  • 1.三数取中法
  • 实现:
private static int getMiddleNum(int[] arr,int left,int right){
	int mid = left + (right - left) >> 1;
	if(arr[left] > arr[right]){
		if(arr[right] > arr[mid]){
			return right;
		}else if(arr[mid] > arr[left]){
			return left;
		}else 
			return mid;
	}else {
		if(arr[right] < arr[mid]){
			return right;
		}else if(arr[mid] < arr[left]){
			return left;
		}else 
			return mid;
	}
}

取得中间大的数,这样可以避免取得基准值都是区间的极值这种情况

  • 总体优化:当未排序区间小于某个阈值(例如16),使用直接插入进行排序
  • 实现:
public static void quickSort(int[] array,int left,int right){
        if(right - left < 16) {
			insertSort(arr,left,right);
		}else {
      	  // 说明区间内至少有16个元素
            int part = partition(array, left, right);
            // 递归排基准值左侧
            quickSort(array, left, part);
            // 递归排基准值右侧
            quickSort(array, part + 1, right);
        }
    }
4.5 非递归实现
public static void quickSort(int[] arr,int left,int right){
	Stack<Integer> s = new Stack<>();
	s.push(right);
	s.push(left);
	while(!s.empty()){
		int start = s.pop();
		int end = s.pop();
		if(right - left > 1){
			int part = partition(arr,start,end);
			s.push(end);
			s.push(mid+1);
			s.push(mid);
			s.push(start);
		}
	}
}

6. 归并排序

6.1 原理

归并排序是采用的先分再归的形式进行排序,首先递归到 区间只有一个数据,然后再进行排序,然后再归到上一层,将两个数在排序,在一直往上归

6.2 实现
	// 方法作用是将一个数组分为 左中右 进行排序  [left,mid),[mid,right) 
	// temp数组中的数字是已经排好序的
	public static void mergeData(int[] arr, int left, int mid, int right, int[] temp) {
        int begin1 = left;
        int begin2 = mid;
        int index = left;
        // 当两个数组中都有数据时,两组数据进行比较
        while (begin1 < mid && begin2 < right) {
            if (arr[begin1] <= arr[begin2]) {
                temp[index++] = arr[begin1++];
            }else {
                temp[index++] = arr[begin2++];
            }
        }
        // 当[mid,right) 已经没数据了 temp数组直接将[left,mid)剩余的数据加进去
        while (begin1 < mid) {
            temp[index++] = arr[begin1++];
        }
        // 当[left,mid) 已经没数据了 temp数组直接将[mid,right)剩余的数据加进去
        while (begin2 < right) {
            temp[index++] = arr[begin2++];
        }
    }

    public static void mergeSort(int[] arr, int left, int right, int[] temp) {
        if (right - left > 1) {
            int mid = left +  ((right - left) >> 1);
            mergeSort(arr, left, mid, temp);
            mergeSort(arr, mid, right, temp);
            mergeData(arr, left, mid, right, temp);
            System.arraycopy(temp, left, arr, left, right - left);
        }
    }
6.3 时间复杂度、空间复杂度、稳定性

时间复杂度:O(NlogN)
空间复杂度:O(N)
稳定性:稳定

6.4 非递归实现
public static void mergeSortNor(int[] arr) {
        int[] temp = new int[arr.length];
        int gap = 1;
        while (gap < arr.length){
            for (int i = 0; i < arr.length; i+= gap*2) {
                int left = i;
                int mid = left + gap;
                int right = mid + gap;
                if(mid > arr.length){
                    mid = arr.length;
                }
                if(right > arr.length){
                    right = arr.length;
                }
                mergeData(arr,left,mid,right,temp);
            }
            System.arraycopy(temp,0,arr,0,arr.length);
            gap <<= 1;
        }
    }
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值