排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。
平时的上下文中,如果提到排序,通常指的是排升序(非降序)。
通常意义上的排序,都是指的原地排序(in place sort)
插入排序
每次选择无序区间的第一个元素,在有序区间内选择合适的位置插入。
定义两个指针 i j,在定义一个temp存储遍历到i的值,从第二个数开始排序,默认第一个为有序,i控制第一个数字,j控制向前比较,当i的值<j的值时,把j的值向后挪动,j–,如果大于则break内循环,将j+1的值变成temp。
/**
* 时间复杂度:最坏:O(n^2),最好O(n)。
* 空间复杂度:O(1)
*/
public static void main(String[] args) {
for (int i = 1; i < elem.length; i++) {
int temp = elem[i];
int j = i-1;
for (; j >= 0 ; j--) {
if (elem[j] > temp){
elem[j + 1] = elem[j];
}else {
break;
}
}
elem[j + 1] = temp;
}
System.out.println(Arrays.toString(elem));
}
希尔排序
希尔排序法又称缩小增量法。希尔排序法的基本思想是:先选定一个整数,把待排序文件中所有记录分成个组,所
有距离为的记录分在同一组内,并对每一组内的记录进行排序。然后,取,重复上述分组和排序的工作。当到达=1
时,所有记录在统一组内排好序。
- 希尔排序是对直接插入排序的优化。
- 当gap > 1时都是预排序,目的是让数组更接近于有序。当gap == 1时,数组已经接近有序的了,这样就会很快。这样整体而言,可以达到优化的效果。我们实现后可以进行性能测试的对比。
public static void shell(int[] array,int gap) {
for (int i = gap; i < array.length; i ++) {
int temp = array[i];
int j = i - gap;
for (; j >= 0 ; j -= gap) {
if (array[j] > temp){
array[j + gap] = array[j];
}else {
break;
}
}
array[j + gap] = temp;
}
}
public static void shellSort(int[] array) {
int[] drr = {5,3,1};
for (int i = 0; i < drr.length; i++) {
shell(array,drr[i]);
}
}
选择排序
每一次从无序区间选出最大(或最小)的一个元素,存放在无序区间的最后(或最前),直到全部待排序的数据元素排完 。
选择排序是从前往后排序,选定一个元素,在从前往后挨个比较,只要小于当前元素,就交换,一次遍历确定当前元素为当前元素到数组尾中的最小值。遍历数组长度的变数,数组就有序了。
/**
* 时间复杂:O(n^2)
* 空间复杂度O(1)
* 不稳点,交换是跨越式交换
* @param array
*/
public void sort(int[] array){
for (int i = 0; i < array.length - 1; i++) {
for (int j = i + 1 ; j < array.length; j++) {
if (array[i] > array[j]){
int temp = array[i];
array[i] = array[j];
array[j] = temp;
}
}
}
}
堆排序
基本原理也是选择排序,只是不在使用遍历的方式查找无序区间的最大的数,而是通过堆来选择无序区间的最大的数。
注意: 排升序要建大堆;排降序要建小堆
堆排序是使用了堆这个数据结构,给定一个数组,先通过这个数组构建一个大顶堆或者小顶堆。(从小到大排序时是大顶堆,反之亦然)。构建好了以后堆顶就是当前数组最大或最小的元素,然后进行排序。
定义一个end,标记当前堆的堆底元素,先把堆顶的元素与end互换位置,当前堆底就是有序的了(虽然只有一个元素,但从小到大排序,最大的在最后,所以是有序的了),然后将end–。
只要重复将堆顶元素与end交换,交换后堆会自动将最大或最小的放到堆顶,所有从后往前数组就变的有序了。
这里有一个模拟堆排序动画
堆排序
/**
* 时间复杂度:O(N*logn)
* 空间复杂度:O(1)
*/
public static void creatHeap(int[] array,int len){
for (int i = (len -2) / 2; i >= 0; i--) {
adjustDown(array,i,len);
}
}
private static void adjustDown(int[] array, int i, int len) {
int parent = i;
int child = parent *2 +1;
while (child < len){
if (child + 1 < len && array[child] > array[child + 1])
child++;
if (array[child] < array[parent]){
int temp = array[child];
array[child] = array[parent];
array[parent] = temp;
parent = child;
child = child * 2+1;
}else {
break;
}
}
}
private static void sort(int[] array){
int end = array.length - 1;
while (end > 0){
int temp = array[0];
array[0] = array[end];
array[end] = temp;
adjustDown(array,0,end);
end--;
}
}
冒泡排序
在无序区间,通过相邻数的比较,将最大的数冒泡到无序区间的最后,持续这个过程,直到数组整体有序
双重循环:定义两个指针,一个控制数组当前当前第几个数字,一个控制需要比较元素,通过比较把当前最大的值冒泡到数组的最后,通过多次冒泡,数组就可变的有序。
冒泡排序是可以优化的:当某一次循环过后数组已经有序了,就可以直接退出,不用继续冒泡。
public void sort(int[] array){
boolean isSort;
for (int i = 0; i < array.length-1; i++) {
isSort = false;
for (int j = 0; j < array.length-1-i; j++) {
if (array[j] > array[j + 1]){
int temp = array[j];
array[j] = array[j+1];
array[j+1] = temp;
isSort = true;
}
}
if (!isSort)break;
}
}
冒泡排序时间复杂度:O(n^2)最好情况为O(n)。
空间复杂度为O(n)。
归并排序
归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide andConquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。
递归把原数组分成一个一个小数组,在拼回来的时候进行排序,新建一个数组存储排序后的数组,遍历两个数组按序放入新建的数组,然后将排好序的数组元素复制到原有数组,就完成了排序。
/**
* 先将数组分成一个一个的数组,在把分开的一个一个排序,然后往上返回,最后成有序的
* 时间复杂度:O(nlogn)
* 空间复杂度:O(n)
* 稳点的排序。
*/
public static void spilit(int[] array,int left,int right){
if (left >= right)return;
int mid = (left + right) / 2;
spilit(array,left,mid);
spilit(array,mid+1,right);
merge(array, left, mid, right);
}
private static void merge(int[] array, int left, int mid, int right) {
int s1 = left;
int s2 = mid + 1;
int[] res = new int[right - left + 1];
int i = 0;
while (s1 <= mid && s2 <= right){
if (array[s1] <= array[s2]){
res[i++] = array[s1++];
}else {
res[i++] = array[s2++];
}
}
while (s1 <= mid){
res[i++] = array[s1++];
}
while (s2 <= right){
res[i++] = array[s2++];
}
for (int j = 0; j < res.length; j++) {
array[left + j] = res[j];
}
}
/**
* 非递归归并排序
* @param array
*/
public static void mergeSort(int[] array) {
for (int gap = 1; gap < array.length; gap *= 2) {
mergeNor(array,gap);
}
}
public static void mergeNor(int[] array,int gap) {
int[] ret = new int[array.length];
int k = 0;//ret的下标
int s1 = 0;
int e1 = s1 + gap - 1;
int s2 = e1 + 1;
int e2 = s2 + gap-1 < array.length ? s2+gap-1 : array.length-1;
//1、肯定是有两个归并段的
while (s2 < array.length) {
//2、对应的s1位置和s2位置进行比较
while (s1 <= e1 && s2 <= e2) {
if(array[s1] <= array[s2]) {
ret[k++] = array[s1++];
}else {
ret[k++] = array[s2++];
}
}
//3、上述第2步在比较的过程当中,肯定会有一个下标先走完一个归并段
//4、判断是谁没走完,把剩下的数据拷贝到结果数组当中
while (s1 <= e1) {
ret[k++] = array[s1++];
}
while (s2 <= e2) {
ret[k++] = array[s2++];
}
//5、接着确定新的s1,e1,s2,e2
s1 = e2 + 1;
e1 = s1 + gap - 1;
s2 = e1 + 1;
e2 = s2+gap-1 < array.length ? s2+gap-1 : array.length-1;
}
while (s1 < array.length){
ret[k++] = array[s1++];
}
for (int j = 0; j < ret.length; j++) {
array[j] = ret[j];
}
}
快速排序
- 从待排序区间选择一个数,作为基准值(pivot);
- Partition: 遍历整个待排序区间,将比基准值小的(可以包含相等的)放到基准值的左边,将比基准值大的(可以包含相等的)放到基准值的右边;
- 采用分治思想,对左右两个小区间按照同样的方式处理,直到小区间的长度 == 1,代表已经有序,或者小区间的长度 == 0,代表没有数据。
定义左右两个指针。先分别指向数组的头尾,然后将左指针的元素当做tep,从右指针开始,不停–,碰到小于tep的就停止,和左指针交换,左指针不停++,遇到大于tep的停止,将值赋予当前右指针的位置,直到两个指针相遇,将tep的值放到相遇的地方。然后继续递归,分为左右两边,左边的right为相遇的-1,右边的left为相遇的+1.最后直至有序。
快速排序在原数组就趋于有序时,会进入到多层递归,可能会产生栈溢出,所以可以在多次排序后直接使用插入排序,因为数组趋于有序时,插入排序的时间复杂度可以趋于O(N)。也可以使用几数取中(例如三数取中):array[left], array[mid], array[right] 大小是中间的为基准值,将mid的值小于left和right的值,这样在递归的时候,就可以在数组偏中间分隔,不会递归很多层。
/**
* 时间复杂度:O(nlogn)最坏:O(n^2)
* 空间复杂度:O(longn)最坏:O(n)
* @param array
* @param low
* @param high
* @return
*/
public static int partition(int[] array,int low,int high) {
if (low >= high)return low;
int temp = array[low];
while (low < high){
while (array[high] > temp && low < high){
high--;
}
array[low] = array[high];
while (array[low] <= temp && low < high){
low++;
}
array[high] = array[low];
}
array[low] = temp;
return low;
}
public static void three_num_mid(int[] array,int left,int right) {
//array[mid] <= array[left] <= array[high]
int mid = (left + right) / 2;
if (array[mid] <= array[left]
&& array[left] <= array[right])return;
}
public static void quick(int[] array,int left,int right) {
if(left >= right) {
return;
}
int partition = partition(array,left,right);
quick(array,left,partition - 1);
quick(array,partition+1,right);
}
public static void quickSort1(int[] array) {
quick(array,0,array.length-1);
}