一、插入排序
算法思想:
从第二个数开始,往前找,如果前面的数比自己大,就交换,最终找到合适的位置就结束了。(这个交换可以理解为向后移位,你玩扑克牌的时候,按大小排序,前面插一张,后面的就要往后挪一点)
时间复杂度:
最好:O(n)
最坏:O(n^2)
平均:O(n)
空间复杂度: O(1)
稳定性:稳定
public static void InserSort(int[] arr){
int tmp;
for(int i=0;i<arr.length-1;i++){
for(int j=i+1;j>0;j--){
if(arr[j] < arr[j-1]){
tmp = arr[j];
arr[j] = arr[j-1];
arr[j-1] = tmp;
} else{
break;
}
}
}
}
换个版本
public static void InsertSort(int[] arr){
for(int i=1;i<arr.length;i++){
int key = arr[i];
int j = i-1;
for(;j>=0 && arr[j] > v;j--){
arr[j+1] = arr[j];
}
arr[j+1] = key;
}
}
二、希尔排序
算法思想:基于直接插入方法的升级版本,目的就是为了先让数组变得更有序,有序了插入排序的时间复杂度就会降低。
时间复杂度:
最好:O(n)
最坏:O(n^2)
平均:O(n^1.3)
空间复杂度: O(1)
稳定性: 不稳定
public static void ShellSort(int[] arr,int length){
int pos = length;
int tmp;
while(true){
pos = pos/2;
for(int k=0;k<pos;k++){
for(int i= k+pos;i<arr.length;i+=pos){
for(int j=i;j>k;j-=pos){
if(arr[j] < arr[j-pos]){
tmp = arr[j];
arr[j] = arr[j-pos];
arr[j-pos] = tmp;
} else {
break;
}
}
}
}
if(pos == 1){
break;
}
}
}
换个版本:
public static void shellSort(int[] array) {
int gap = array.length;
while (gap > 1) {
insertSortGap(array, gap);
gap = (gap / 3) + 1; // OR gap = gap / 2;
}
insertSortGap(array, 1);//就是直接插入排序了
}
private static void insertSortGap(int[] array, int gap) {
for (int i = 1; i < array.length; i++) {
int v = array[i];
int j = i - gap;
for (; j >= 0 && array[j] > v; j -= gap) {
array[j + gap] = array[j];
}
array[j + gap] = v;
}
}
三、冒泡排序
算法思想:每次让最大的冒出来;这么说吧,从最后一个往前找,发现左边的比后面的大,就交换,然后第一趟,把最小的放到了前面,第二趟,把次小的放到了前面。
时间复杂度:
最好:O(n)
最坏:O(n^2)
平均:O(n^2)
空间复杂度: O(1)
稳定性: 稳定
public static void BubbleSort(int[] arr){
int tmp;
for(int i=0;i<arr.length;i++){
for(int j= arr.length-1;j>i;j--){
if(arr[j] < arr[j-1]){
tmp = arr[j];
arr[j] = arr[j-1];
arr[j-1] = tmp;
}
}
}
}
升级版:主要是为了解决没有交换,说明已经有序,直接跳出。
public static void BubbleSort(int[] arr){
int tmp;
boolean flag = false;
for(int i=0;i<arr.length;i++){
flag = false;
for(int j= arr.length-1;j>i;j--){
if(arr[j] < arr[j-1]){
tmp = arr[j];
arr[j] = arr[j-1];
arr[j-1] = tmp;
flag = true;
}
}
if(!flag){
break;
}
}
}
四、选择排序
算法思想:打擂台方法,若为升序,第一个肯定最小,然后我找后面有比它还小的吗,如果有更改最小标志位,直到找到最小的,然后交换。
时间复杂度: O(n^2)
空间复杂度: O(1)
稳定性: 不稳定
public static void SelectSort(int arr[]){
int tmp=0;
for(int i=0;i<arr.length-1;i--){
int minIndex = i;
for(int j=i+1;j<arr.length;j++){
if(arr[minIndex] > arr[j]){
minIndex = j;
}
}
if(minIndex != i){
tmp = arr[i];
arr[i] = arr[minIndex];
arr[minIndex] = tmp;
}
}
}
五、快速排序
算法思想:先找出一个基准值,找到之后,比他小的放左边,比他大的放右边,排好序后,把临界点的值给返回来。(看代码说话),分治法的思想。
时间复杂度:
最好:O(nlog(n))
最坏:O(n^2)
平均:O(nlog(n))
空间复杂度: 最好平均都是O(log(n)),最坏O(n)
稳定性: 不稳定
递归版本
public static void QuickSort(int[] arr){
QuickSortHelper(arr,0,arr.length-1);
}
private static void QuickSortHelper(int arr[],int left,int right){
if(left >= rigth){ //递归结束标志
return;
}
int partIndex = partition(arr,left,right);//找出临界值
QuickSortHelper(arr,left,partIndex-1);//排序左半部分
QuickSortHelper(arr,partIndex+1,right);//排序右半部分
}
private static void partition(int[] arr,int left,int right){
int BaseIndex = right; //以最右边为基准点
int BaseValue = arr[right];
while(left < BaseIndex){
while(left < BaseIndex && arr[left] <= BaseValue ){
left++; //从左往右找到第一个大于基准值的
}
while(left < BaseIndex && BaseIndex <= arr[BaseIndex]){
BaseIndex--; //从右边往左找第一个小于基准值的
}
swap(arr,left,BaseIndex);//把两个交换
}
swap(arr,left,right);//把临界点的那个值放到最右边去
return left;//返回临界值的座标
]
private static void swap(int[] arr,int left,int right){
int tmp = arr[left];
arr[left] = arr[right];
arr[rigth] = tmp;
}
非递归版本:
public static void quickSort(int[] array) {
Stack<Integer> stack = new Stack<>();
stack.push(array.length - 1);
stack.push(0);
while (!stack.isEmpty()) {
int left = stack.pop();
int right = stack.pop();
if (left >= right) {
continue;
}
int pivotIndex = partition(array, left, right);
stack.push(right);
stack.push(pivotIndex + 1);
stack.push(pivotIndex - 1);
stack.push(left);
}
}
private static void partition(int[] arr,int left,int right){
int BaseIndex = right;
int BaseValue = arr[right];
while(left < BaseIndex){
while(left < BaseIndex && arr[left] <= BaseValue ){
left++;
}
while(left < BaseIndex && BaseIndex <= arr[BaseIndex]){
BaseIndex--;
}
swap(arr,left,BaseIndex);
}
swap(arr,left,right);
return left;
]
private static void swap(int[] arr,int left,int right){
int tmp = arr[left];
arr[left] = arr[right];
arr[rigth] = tmp;
}
六、归并排序
算法思想:是分治法的一个非常典型的应用,将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并
时间复杂度: O(n*log(n))
空间复杂度: O(n)
稳定性: 稳定
public static void MergeSort(int arr[]){
MergeSortHelper(arr,0,arr.length);
}
private static void MergeSort(int[] arr,int left,int right){
if(left >= right || rigth-left == 1){
return;
}
int mid = (left+right)/2;
MergeSort(arr,left,mid);//排序左边
MergeSort(arr,mid,right);//排序右边
merge(arr,left,mid,right);//合并
}
private static void merge(int[] arr, int left,int mid ,int right){
int length = right-left;
int[] Output = new int[length];//搞个临时数组,就排序的
int OutputIndex = 0;
int i=left;
int j=mid;
while (i < mid && j < right){
if(arr[i] <= arr[j]){
Output[OutputIndex++] = arr[i++];插入排序
} else {
Output[OutputIndex++] = arr[j++];
}
}
while(i < mid){
Output[OutputIndex++] = arr[i++];//看看哪个子串没空,补上
}
while(j < right){
Output[OutputIndex++] = arr[j++];
}
for(int k=0;k<OutputIndex;k++){
arr[left+k] = Output[k]; //赋值回原数组
}
}
七、堆排序
算法思想:基本原理也是选择排序,只是不在使用遍历的方式查找无序区间的最大的数,而是通过堆来选择无序区间的最大的数。
堆:其实有一个类似于完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。
注意: 排升序要建大堆;排降序要建小堆
时间复杂度: O(n*log(n))
空间复杂度: O(1)
稳定性: 不稳定
public class HeapSort {
/**
* 选择排序-堆排序
* @param array 待排序数组
* @return 已排序数组
*/
public static int[] heapSort(int[] array) {
//这里元素的索引是从0开始的,所以最后一个非叶子结点array.length/2 - 1
for (int i = array.length / 2 - 1; i >= 0; i--) {
adjustHeap(array, i, array.length); //调整堆
}
// 上述逻辑,建堆结束
// 下面,开始排序逻辑
for (int j = array.length - 1; j > 0; j--) {
// 元素交换,作用是去掉大顶堆
// 把大顶堆的根元素,放到数组的最后;换句话说,就是每一次的堆调整之后,都会有一个元素到达自己的最终位置
swap(array, 0, j);
// 元素交换之后,毫无疑问,最后一个元素无需再考虑排序问题了。
// 接下来我们需要排序的,就是已经去掉了部分元素的堆了,这也是为什么此方法放在循环里的原因
// 而这里,实质上是自上而下,自左向右进行调整的
adjustHeap(array, 0, j);
}
return array;
}
/**
* 整个堆排序最关键的地方
* @param array 待组堆
* @param i 起始结点
* @param length 堆的长度
*/
public static void adjustHeap(int[] array, int i, int length) {
// 先把当前元素取出来,因为当前元素可能要一直移动
int temp = array[i];
for (int k = 2 * i + 1; k < length; k = 2 * k + 1) { //2*i+1为左子树i的左子树(因为i是从0开始的),2*k+1为k的左子树
// 让k先指向子节点中最大的节点
if (k + 1 < length && array[k] < array[k + 1]) { //如果有右子树,并且右子树大于左子树
k++;
}
//如果发现结点(左右子结点)大于根结点,则进行值的交换
if (array[k] > temp) {
swap(array, i, k);
// 如果子节点更换了,那么,以子节点为根的子树会受到影响,所以,循环对子节点所在的树继续进行判断
i = k;
} else { //不用交换,直接终止循环
break;
}
}
}
/**
* 交换元素
* @param arr
* @param a 元素的下标
* @param b 元素的下标
*/
public static void swap(int[] arr, int a, int b) {
int temp = arr[a];
arr[a] = arr[b];
arr[b] = temp;
}
}
【理解多少,全凭个人造化。】
补充
其他非主流排序,就说说算法吧,当做兴趣看一看。
1.基数排序
就是建立一个长数组,数字是多少就放在对应下标,然后里面的数字就是元素的个数,然后遍历一遍数组就得到了排序的结果。优劣性,想想就知道了,数字特别大岂不是凉凉,算是一种空间换时间的做法。(PS:数字大也是可以解决的,选择一个基数,除以这个数来放,有的哈希的味道)
2.睡眠排序
这个算好玩吧,这个是多线程方面的,就是进来一个数字,我就创建一个线程,然后让线程休眠,休眠的时间就是这个数字,到了这个时间,线程显示出这个数字就销毁。这排序可以自己写着玩玩,缺点一样的,数字大休眠到天荒地老。这好像算时间换空间把
3.猴子排序
这纯粹是搞笑来的,据说只要猴子够多,让猴子打字,猴子就能写出一本《哈姆雷特》,而猴子排序也是这样的。就是随机,把数组所有的数字随机排序一下,检查是否有序,没有的话就再来一次。我前面写过C语言版本的