0、复杂度及稳定性
时间复杂度 | 空间复杂度 | 稳定性(相等元素相对顺序不变) | |
冒泡排序 | 时间复杂度为O(n^2) 最坏/平均:O(n^2) 最好:O(n),序列有序 | O(1) | 稳定 |
插入排序 | 时间复杂度为O(n^2) 最坏/平均:O(n^2) 最好:O(n),序列有序 | O(1) | 稳定 |
选择排序 | O(n^2) | O(1) | 不稳定 |
希尔排序 | O(nlogn) <O( )<O(n^2) | O(1) | 不稳定 |
快速排序 | O(nlogn) 最坏:O(n^2),逆序 | O(logn) | 不稳定 |
归并排序 | O(nlogn) | O(n) | 稳定 |
堆排序 | O(nlogn) | O(1) | 不稳定 |
桶排序 | 最好:O(n) 最坏/平均,与数据分布有关 | O(n+k) k是桶的数量 | 稳定 |
计数排序 | O(n+k) | O(k) k是待排序序列中最大值与最小值的差 | 稳定 |
基数排序 | O(d(n+k)) d为位数,n为数据总数 k为最大数的位数 | O(n+k) k为最大数的位数 | 稳定 |
一、冒泡排序
比较相邻的元素,若顺序错误则交换
public void bubbleSort(int[] array) {for (int i = 0; i < arr.length - 1; i++) { for (int j = 0; j < arr.length - i - 1; j++) { if (array[j] > array[j+1]) { // 交换 array[j] 和 array[j+1] int temp = array[j]; array[j] = array[j+1]; array[j+1] = temp; } } }
}
二、插入排序
将一个待排序的元素按其大小插入到已排序的序列中的适当位置,直到全部插入完
public void insertionSort(int[] array) { int n = array.length; for (int i = 1; i < n; ++i) { int key = array[i]; int j = i - 1; while (j >= 0 && array[j] > key) { array[j + 1] = array[j]; j = j - 1; } array[j + 1] = key; }
}
三、选择排序
在未排序序列中找到最小元素,存放到排序序列的起始位置。再从剩余未排序元素中继续寻找最小元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。
public void selectionSort(int[] array) { for (int i = 0; i < array.length - 1; i++) { // 找到最小元素的索引 int minIndex = i; for (int j = i + 1; j < array.length; j++) { if (array[j] < array[minIndex]) { minIndex = j; } } // 将找到的最小元素交换到已排序序列的末尾 int temp = array[minIndex]; array[minIndex] = array[i]; array[i] = temp; }
}
四、希尔排序
插入排序的一种更高效的改进版
public void shellSort(int[] array) { int n = array.length; int gap, i, j, temp; // Start with a big gap, then reduce the gap for (gap = n / 2; gap > 0; gap /= 2) { for (i = gap; i < n; i += 1) { temp = array[i]; for (j = i; j >= gap && array[j - gap] > temp; j -= gap) { array[j] = array[j - gap]; } array[j] = temp; } }
}
五、快速排序
采用分治法。选择一个“基准”元素,通过一趟排序将待排序的数据分割成独立的两部分,其中一部分的所有数据都比基准元素小,另一部分的所有数据都比基准元素大。以此类推,达到有序
public void quickSort(int[] array, int low, int high) { if (low < high) { // 找到基准元素的正确位置 int pi = partition(array, low, high); // 分别对基准元素两侧的子序列进行递归排序 quickSort(array, low, pi - 1); quickSort(array, pi + 1, high); }
} /* 基准元素分割 */
public int partition(int[] array, int low, int high) { // 选择最右侧的元素作为基准元素 int pivot = array[high]; int i = low - 1; // 指向比基准元素小的元素 for (int j = low; j < high; j++) { // 如果当前元素小于或等于基准元素 if (array[j] <= pivot) { i++; // 交换两个元素 int temp = array[i]; array[i] = array[j]; array[j] = temp; } } // 将基准元素放到正确的位置 int temp = array[i + 1]; array[i + 1] = array[high]; array[high] = temp; return i + 1;
}
六、归并排序
先递归分解数组,再将已有序的子序列合并,得到完全有序的序列
public void mergeSort(int[] array, int left, int right) { if (left < right) { // 找到中间位置 int mid = (left + right) / 2; // 对左半部分进行归并排序 mergeSort(array, left, mid); // 对右半部分进行归并排序 mergeSort(array, mid + 1, right); // 合并左右两部分 merge(array, left, mid, right); }
} public void merge(int[] array, int left, int mid, int right) { // 创建一个临时数组来辅助归并操作 int[] temp = new int[right - left + 1]; // 左指针和右指针分别指向左半部分和右半部分的起始位置 int i = left; int j = mid + 1; int k = 0; // 合并两个有序数组到临时数组 while (i <= mid && j <= right) { if (array[i] <= array[j]) { temp[k++] = array[i++]; } else { temp[k++] = array[j++]; } } // 将左半部分剩余的元素复制到临时数组 while (i <= mid) { temp[k++] = array[i++]; } // 将右半部分剩余的元素复制到临时数组 while (j <= right) { temp[k++] = array[j++]; } // 将临时数组中的元素复制回原数组 for (i = 0; i < temp.length; i++) { array[left + i] = temp[i]; }
}
七、堆排序
利用堆进行排序
public class Heap <T extends Comparable<T>>{//定义一个数组来存储堆中的元素private T[] items;//记录堆中元素个数private int Num;public Heap(int capacity){//因为T继承了Comparable接口,所以这里是new Comparable类型数组this.items = (T []) new Comparable[capacity+1]; this.Num = 0;}//判断堆中索引i处的元素是否小于索引j处的元素private boolean less(int i, int j){return items[i].compareTo(items[j]);}//交换堆中i索引和j索引处的值private void exchange(int i, int j){T temp = items[i];items[i] = items[j];items[j] = temp;}//往堆中插入一个元素public void insert(T t) {//因为数组数量Num初始化为0,而这里堆中第一个元素item[0]是不存放任何数值的,//所以使用++Num跳过了第一个items【0】items[++Num] = t;rise(Num);}//上浮算法,使索引k处的元素能在堆中处于一个正确的位置private void rise(int k){//通过循环不断比较当前结点的值和其父结点的值,如果当前结点大于父结点就交换两者位置while(k>1){//比较当前结点和其父结点if(less(k/2,k)){exchange(k/2,k);k = k/2;}else{break;}}}//删除堆中最大的元素,并返回这个最大元素public T delMax(){T max = items[1];//交换索引1处元素和最大索引处的元素,让完全二叉树最右侧的元素变为临时根结点exchange(1,Num);//删除交换操作后的最大索引处的元素items[Num] = null;//元素个数减1Num--;//通过下沉算法,重新排列堆sink(1);return max;}//下沉算法,使索引k处的元素能在堆中处于一个正确的位置private void sink(int k){//通过循环不断比较当前k结点和其左子结点2*k以及右子结点2*k+1处的较大值的元素大小//当前结点小,则交换与子节点中最大值的位置while(2*k<=Num){//获取当前结点的子结点的最大结点int max;if(2*k+1<=Num){ //判断当前结点是否有右子结点if(2*k<2*k+1){max = 2*k+1;}else{max = 2*k;}}else{max = 2*k;}//比较当前结点和较大结点的值if(!less(k,max)){break;}//交换k索引的值和max索引处的值exchange(k,max);//交换k的值k = max;}}
}
八、桶排序
1、数据分桶,桶编号 = (数组元素 - 最小值) * (桶个数 - 1) / (最大值 - 最小值)
2、每个桶排序
3、遍历
/*** 桶排序**/
public static void bucketsort(int[] arr, int bucketSize) {// 初始化最大最小值int max = Integer.MIN_VALUE;int min = Integer.MAX_VALUE;// 找出最小值和最大值for (int num : arr) {max = Math.max(max, num);min = Math.min(min, num);}// 创建bucketSize个桶List<List<Integer>> bucketList = new ArrayList<>();// 声明五个桶for (int i = 0; i < bucketSize; i++) {bucketList.add(new ArrayList<>());// 确定桶的格式为ArrayList}// 将数据放入桶中for (int num : arr) {// 确定元素存放的桶号int bucketIndex = (num - min) * (bucketSize - 1) / (max - min);// 将元素存入对应的桶中List<Integer> list = bucketList.get(bucketIndex);list.add(num);}// 遍历每一个桶for (int i = 0, arrIndex = 0; i < bucketList.size(); i++) {List<Integer> list = bucketList.get(i);// 对每一个桶排序list.sort(null);for (int value : list) {arr[arrIndex++] = value;}}
}
九、计数排序
/*** 计数排序**/
public static void countingSort(int[] arr) {if (arr.length == 0) {return;}// 原数组拷贝一份int[] copyArray = Arrays.copyOf(arr, arr.length);// 初始化最大最小值int max = Integer.MIN_VALUE;int min = Integer.MAX_VALUE;// 找出最小值和最大值for (int num : copyArray) {max = Math.max(max, num);min = Math.min(min, num);}// 新开辟一个数组用于统计每个元素的个数(范围是:最大数-最小数+1)int[] countArray = new int[max - min + 1];// 增强for循环遍历for (int num : copyArray) {// 加上最小偏差是为了让最小值索引从0开始,同时可有节省空间,每出现一次数据就加1// 真实值+偏差=索引值countArray[num - min]++;}// 获取数组的长度int length = countArray.length;// 计数数组变形,新元素的值是前面元素累加之和的值for (int i = 1; i < length; i++) {countArray[i] = countArray[i] + countArray[i - 1];}// 遍历拷贝数组中的元素,填充到原数组中去,从后往前遍历for (int j = copyArray.length - 1; j >= 0; j--) {// 数据对应计数数组的索引int countIndex = copyArray[j] - min;// 数组的索引获取//(计数数组的值n就是表示当前数据前有n-1个数据,数组从0开始,故当前元素的索引就是n-1)int index = countArray[countIndex] - 1;// 数组中的值直接赋值给原数组arr[index] = copyArray[j];// 计数数组中,对应的统计值减1countArray[countIndex]--;}
}
十、基数排序
/*** 基数排序**/
public static void radixSort(int[] arr) {// 初始化最大值int max = Integer.MIN_VALUE;// 找出最大值for (int num : arr) {max = Math.max(max, num);}// 我们这里是数字,所以初始化10个空间,采用LinkedListLinkedList<Integer>[] list = new LinkedList[10];for (int i = 0; i < 10; i++) {list[i] = new LinkedList<>();// 确定桶的格式为ArrayList}// 个位数:123 / 1 % 10 = 3// 十位数:123 / 10 % 10 = 2// 百位数: 123 / 100 % 10 = 1for (int divider = 1; divider <= max; divider *= 10) {// 分类过程(比如个位、十位、百位等)for (int num : arr) {int no = num / divider % 10;list[no].offer(num);}int index = 0; // 遍历arr原数组// 收集的过程for (LinkedList<Integer> linkedList : list) {while (!linkedList.isEmpty()) {arr[index++] = linkedList.poll();}}}
}
- 计数排序:每个桶只存储单一键值
- 桶排序:每个桶存储一定范围的数值
- 基数排序:根据键值的每位数字来分配桶