排序:就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。
稳定性:假设一组序列中,有两个相同的元素,在排序之后,两个相同元素的前后顺序颠倒了,说明这个排序算法是不稳定的,反之。
思路:把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,得到
一个新的有序序列 。
/*
* 时间复杂度:最坏:o(N^2)
* 最好:O(N) 数据都有序,j不会往前走,直接break
* 当数据趋于有序的时候,排序速度非常快
* 一般场景就是数据基本有序,建议使用直接插入排序
* 空间复杂度:O(1)
* 稳定性:稳定
* 如果一个排序是稳定的,那么就可以实现为不稳定的,
* 但是如果一个排序本身就是不稳定的,那么不能变成稳定的
*
* */
//插入排序
public static void insertSort(int[] array) {
for (int i = 1; i < array.length; i++) {
int temp = array[i];
int j = i - 1;
for (; j >= 0; j--) {
if (array[j] > temp) {
array[j + 1] = array[j];
} else {
break;
}
}
array[j + 1] = temp;
}
}
基本思想:希尔排序是选的一个增量值分组,然后对每组使用直接插入排序算法排序,随着增量值减小,每组包含的值也越来越多,当增量值减为1时,整个序列在一组内排序
上代码:
//希尔排序 每组进行插入排序
/*时间复杂度:O(N^1.3) - O(N ^ 1.5)
*空间复杂度:o(1)
* 稳定性:不稳定的排序
**/
public static void shellSort(int[] array) {
int gap = array.length;
while (gap > 1) {
gap /= 2;
shell(array, gap);
}
}
//插入排序 ------->gap
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;
}
}
基本思想:在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。
(1)第一种
/*选择排序
*时间复杂度:O(N^2)
* 空间复杂度:o(1)
* 稳定性:不稳定
*
* */
public static void selectSort(int[] arrays) {
for (int i = 0; i < arrays.length; i++) {
int minIndex = i;
for (int j = i + 1; j < arrays.length; j++) {
if (arrays[j] < arrays[minIndex]) {
minIndex = j;
}
}
swap(arrays, i, minIndex);
}
}
private static void swap(int[] array, int i, int j) {
int tmp = array[i];
array[i] = array[j];
array[j] = tmp;
}
(2)前面是用了一个minIndex去找最小值然后交换,最后排序
那么如果用两个一个minIndex去找最小值,一个maxIndex去找最大值,然后用left指向前,right指向后,比如说是升序那就minIndex和left交换,maxIndex和right交换,这样效率应该是比第一种方法要快
基本思想:堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法,它是选择排序的一种。它是通过堆来进行选择数据。需要注意的是排升序要建大堆,排降序建小堆。
/*堆排序
*时间复杂度:O(Nlogn)
* 空间复杂度:O(1)
* 稳定性:不稳定
* */
public static void heapSort(int[] array) {
createBigHeap(array);
int end = array.length - 1;
while (end > 0) {
swap(array, 0, end);
shiftDown(array, 0, end);
end--;
}
}
private static void createBigHeap(int[] array) {
for (int parent = (array.length - 1 - 1) / 2; parent >= 0; parent--) {
shiftDown(array, parent, array.length);
}
}
private static void shiftDown(int[] array, int parent, int len) {
int child = parent * 2 + 1;
//至少有左孩子
while (child < len) {
if (child + 1 < len && array[child] < array[child + 1]) {
//有右孩子,且右孩子最大
child++;
}
if (array[child] > array[parent]) {
swap(array, child, parent);
parent = child;
child = 2 * parent + 1;
} else {
//child比parent小,不需要调整
break;
}
}
}
基本思想:冒泡排序是一种简单的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果它们的顺序错误就把它们交换过来。重复的进行上面的步骤,直到没有再需要交换的
/*
* 冒泡排序
* 时间复杂度:不考虑优化 O(N^2),优化后,o(N)
*空间复杂度o(1)
* 稳定性:稳定
* */
public static void bubbleSort(int[] array) {
for (int i = 0; i < array.length; i++) {
//考虑优化如果某一轮中没有发生交换,则证明已经有序,不必进行后面轮数
boolean flg = false;
for (int j = 0; j < array.length - 1 - i; j++) {
if (array[j] > array[j + 1]) {
swap(array, j, j + 1);
flg = true;
}
}
if (flg == false) {
return;
}
}
}
private static void swap(int[] array, int i, int j) {
int tmp = array[i];
array[i] = array[j];
array[j] = tmp;
}
基本思想:任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,
左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,
然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。
在写递归的过程中可联想 二叉树的前序遍历 找基准值 进行区间划分
//快速排序
/*
* 时间复杂度:N*logN
* 最好情况:N*logN
* 最坏情况:N^2 有序 倒序 (只有左树或者只有右树)
* 空间复杂度:
* 最好情况:logN
* 最坏情况:N
* 稳定性:不稳定
*
* */
//1.递归实现快速排序
public static void quickSort(int[] array) {
quick(array, 0, array.length - 1);
}
private static void quick(int[] array, int start, int end) {
//递归结束条件
//为什么取大于号 :1,2,3,4 越界
if (start >= end) {
return;
}
//基准
int pivot = partition(array, start, end);//划分
quick(array, start, pivot - 1);
quick(array, pivot + 1, end);
}
//插入排序
public static void insertSort2(int[] array, int start, int end) {
for (int i = start + 1; i <= end; i++) {
int temp = array[i];
int j = i - 1;
for (; j >= 0; j--) {
if (array[j] > temp) {
array[j + 1] = array[j];
} else {
break;
}
}
array[j + 1] = temp;
}
}
//基准 挖坑法
private static int partition(int[] array, int left, int right) {
int tmp = array[left];
while (left < right) {
//left
//“ = ”必须取,不然会发生死循环:6 23 1 3 6
while (left < right && array[right] >= tmp) {
right--;
}
array[left] = array[right];
while (left < right && array[left] <= tmp) {
left++;
}
array[right] = array[left];
}
array[left] = tmp;
return left;
}
//Hoare法
private static int partition2(int[] array, int left, int right) {
int tmp = array[left];
int i = left;
while (left < right) {
//为什么要先从右边走,因为左边先停下来就会比tmp大,与i位置交换就会把比tmp大的换到前面
while (left < right && array[right] >= tmp) {
right--;
}
while (left < right && array[left] <= tmp) {
left++;
}
swap(array, left, right);
}
swap(array, left, i);
return left;
}
//1.递归实现快速排序
public static void quickSort(int[] array) {
quick(array, 0, array.length - 1);
}
private static void quick(int[] array, int start, int end) {
//递归结束条件
//为什么取大于号 :1,2,3,4 越界
if (start >= end) {
return;
}
//优化2:解决减少递归的次数
if (end - start + 1 <= 14) {
//假设有十万条数据,还剩十几条,那么数据基本有序,用插入排序更高效
insertSort2(array, start, end);
return;
}
// 三数取中法优化1:不用每一次都用start,而是从start ,mid ,end中取一个中间数来作为基准然后将它与start位置的数交换位置即可,
// 后续不用改变
int i = minThree(array, start, end);
swap(array, i, start);
//基准
int pivot = partition2(array, start, end);//划分
quick(array, start, pivot - 1);
quick(array, pivot + 1, end);
}
//三数取中法
private static int minThree(int[] array, int start, int end) {
int mid = (start + end) / 2;
if (start < end) {
if (mid < start) {
return start;
} else if (mid > end) {
return end;
} else {
return mid;
}
} else {
if (mid < end) {
return end;
} else if (mid > start) {
return start;
} else {
return mid;
}
}
}
//2.非递归实现快速排序
/*
* 时间复杂度:O(Nlog n)
* 空间复杂度:O(log n)
* 稳定性:不稳定
* */
public static void quickSort2(int[] array) {
Deque<Integer> stack = new LinkedList<>();
int left = 0;
int right = array.length - 1;
int pivot = partition(array, left, right);
//保证左边与右边都大于两个元素,才可以排序,只有一个元素就不用排序了
if (pivot > left + 1) {
stack.push(left);
stack.push(pivot - 1);
}
if (pivot < right - 1) {
stack.push(pivot + 1);
stack.push(right);
}
while (!stack.isEmpty()) {
right = stack.pop();
left = stack.pop();
pivot = partition(array, left, right);
//保证左边与右边都大于两个元素,才可以排序,只有一个元素就不用排序了
if (pivot > left + 1) {
stack.push(left);
stack.push(pivot - 1);
}
if (pivot < right - 1) {
stack.push(pivot + 1);
stack.push(right);
}
}
}
基本思想:归并排序是建立在归并操作上的一种有效的排序算法,将已有序的子序列合并,得到完全有序的序列;也就是先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。
//归并排序
public static void mergeSort(int[] array) {
mergeSortFunc(array, 0, array.length - 1);
}
private static void mergeSortFunc(int[] array, int start, int end) {
//和快速排序原因一样
if (start >= end) {
return;
}
int mid = (end + start) / 2;
//分
mergeSortFunc(array, start, mid);
mergeSortFunc(array, mid + 1, end);
//并
merge(array, start, end, mid);
}
private static void merge(int[] array, int start, int end, int mid) {
int s1 = start;
int s2 = mid + 1;
//tmp数组下标
int k = 0;
//合并成一个临时数组
int[] tmp = new int[end - start + 1];
while (s1 <= mid && s2 <= end) {
if (array[s1] <= array[s2]) {
tmp[k++] = array[s1++];
} else {
tmp[k++] = array[s2++];
}
}
//s2数组为空时,s1还有数据
while (s1 <= mid) {
tmp[k++] = array[s1++];
}
//同样的
while (s2 <= end) {
tmp[k++] = array[s2++];
}
//将tmp临时数组数据给原数组
for (int i = 0; i < tmp.length; i++) {
//这里start+i意思是,不一定start都是0下标开始的
array[i + start] = tmp[i];
}
}
非递归实现归并排序:
//非递归实现归并排序
public static void mergeSort2(int[] array) {
int gap = 1;
while (gap < array.length) {
//i += gap * 2,当前组拍序好后,i往后移动,排序下一组
for (int i = 0; i < array.length; i += gap * 2) {
int left = i;
int mid = i + gap - 1;
if (mid >= array.length) {避免越界异常
mid = array.length - 1;
}
int right = mid + gap;//
if (right >= array.length) {//避免越界异常
right = array.length - 1;
}
merge(array, left, right, mid);
}
//间隙为1的每组排序好后,改为间隙为2的每组,重复上述步骤,接着排序下一组,直到gap
//超过array.lengh,就排序好了
gap *= 2;
}
}
//计数排序
/*
* 时间复杂度:O(N+范围)
* 空间复杂度:
*
* */
public static void countSort(int[] array) {
//1遍历数组,找到数组最大值和最小值
int min = array[0];
int max = array[0];
for (int i = 1; i < array.length; i++) {
if (array[i] < min) {
min = array[i];
}
if (array[i] > max) {
max = array[i];
}
}
//2确定计数数组的长度
int len = max - min + 1;
int[] count = new int[len];
//3遍历array数组,在计数数组中记录数字出现的个数
for (int i = 0; i < array.length; i++) {
count[array[i] - min]++;
}
//根据计数数组,将数字按照数组顺序重新返回array数组
int index = 0;
for (int i = 0; i < count.length; i++) {
while (count[i] > 0) {
//这里i+min:在array中加上min,反映真实数据
array[index] = i + min;
index++;
count[i]--;
}
}
}
基本思想:基数排序是利用分配和收集两种基本操作。
基数排序是一种按记录关键字的各位值逐步进行排序的方法。
此种排序一般适用于记录的关键字为整数类型的情况。所有对于字符串和文字排序不适合。
private static void radixSort(int[] arr) {
//待排序列最大值
int max = arr[0];
int exp;//指数
//计算最大值
for (int anArr : arr) {
if (anArr > max) {
max = anArr;
}
}
//从个位开始,对数组进行排序
for (exp = 1; max / exp > 0; exp *= 10) {
//存储待排元素的临时数组
int[] temp = new int[arr.length];
//分桶个数
int[] buckets = new int[10];
//将数据出现的次数存储在buckets中
for (int value : arr) {
//(value / exp) % 10 :value的最底位(个位)
buckets[(value / exp) % 10]++;
}
//更改buckets[i],
for (int i = 1; i < 10; i++) {
buckets[i] += buckets[i - 1];
}
//将数据存储到临时数组temp中
for (int i = arr.length - 1; i >= 0; i--) {
temp[buckets[(arr[i] / exp) % 10] - 1] = arr[i];
buckets[(arr[i] / exp) % 10]--;
}
//将有序元素temp赋给arr
System.arraycopy(temp, 0, arr, 0, arr.length);
}
}
基本思想:将要排序的数据分到几个有序的桶里,每个桶里的数据再单独进行排序。桶内排完序之后,再把每个桶里的数据按照顺序依次取出,组成的序列就是有序的了
public static void bucketSort(int[] arr){
// 计算最大值与最小值
int max = Integer.MIN_VALUE;
int min = Integer.MAX_VALUE;
for(int i = 0; i < arr.length; i++){
max = Math.max(max, arr[i]);
min = Math.min(min, arr[i]);
}
// 计算桶的数量
int bucketNum = (max - min) / arr.length + 1;
ArrayList<ArrayList<Integer>> bucketArr = new ArrayList<>(bucketNum);
for(int i = 0; i < bucketNum; i++){
bucketArr.add(new ArrayList<Integer>());
}
// 将每个元素放入桶
for(int i = 0; i < arr.length; i++){
int num = (arr[i] - min) / (arr.length);
bucketArr.get(num).add(arr[i]);
}
// 对每个桶进行排序
for(int i = 0; i < bucketArr.size(); i++){
Collections.sort(bucketArr.get(i));
}
// 将桶中的元素赋值到原序列
int index = 0;
for(int i = 0; i < bucketArr.size(); i++){
for(int j = 0; j < bucketArr.get(i).size(); j++){
arr[index++] = bucketArr.get(i).get(j);
}
}
}