排序
八种排序的时间复杂度:
排序法 | 平均时间 | 最差情形 | 稳定度 | 额外空间 | 备注 |
---|---|---|---|---|---|
冒泡 | O(n2) | O(n2) | 稳定 | O(1) | n小时较好 |
选择 | O(n2) | O(n2) | 不稳定 | O(1) | n小时较好 |
插入 | O(n2) | O(n2) | 稳定 | O(1) | 大部分已排序时较好 |
基数 | O(logRB) | O(logRB) | 稳定 | O(n) | B是真数(0-9),R是基数(个十百) |
计数 | O(n+k) | O(n+k) | 稳定 | O(n+k) | n的范围区间小较好 |
Shell | O(n1.3) | O(n2) | 不稳定 | O(1) | s是所选分组 |
快速 | O(nlogn) | O(n2) | 不稳定 | O(nlogn) | n大时较好 |
归并 | O(nlogn) | O(nlogn) | 稳定 | O(1) | n大时较好 |
堆 | O(nlogn) | O(nlogn) | 不稳定 | O(1) | n大时较好 |
核心: 插入排序、堆排序、归并排序、快速排序
这里写目录标题
1、快速排序(交换)
核心将确定一个中间值,每一次排序都要将数据分割成独立的两部分,其中一部分的所有数据都要比另一部分的所有数据小,然后按照此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行。
基本思想
1、选定Pivot中心轴(默认选择第一个数)
2、将大于Pivot的数字放在Pivot的右边
3、将小于Pivot的数字放在Pivot的左边
4、分别对左右子序列重复前三部操作。
一共就两种情况找到左边 比中心轴小的数,找到就替换,没就right–, 接着找 右边比中心轴大的数,找到就替换,没就left++。
private static void quick_sort(int[] arr, int L, int R) {
if(L >= R) return;
int left =L;
int right = R;
int pr = arr[left];
while (left < right){
while (left < right && pr <= arr[right]){
right -- ;
}
if (left < right){
arr[left] = arr[right];
}
while (left < right && pr >= arr[left]){
left++;
}
if (left < right){
arr[right] = arr[left];
}
if (left == right){
arr[left] = pr;
}
}
quick_sort(arr,L,left-1);
quick_sort(arr,left+1,R);
}
2、冒泡排序(交换)
冒泡排序法:把N个数通过==N-1==轮排序,升序排序中大的数往下沉,小的数往上浮;降序排序中小的数往下沉,大的数往上浮
特点:每次比较会把最大的数下层到最低(最后面),所以每次比较的次数都会比前一次少一次。
核心代码:双重for循环+ swap
public static void bubb_sort(int[] arr){
int num = arr.length-1;
for (int i=0; i<num; i++ ){
for (int j=0; j<num-i; j++ ){
if(arr[j] > arr[j+1]){
int temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
}
3、选择排序(选择)
选择排序特点:在排序中每一轮比较会把最小的数移到最前,所以相互比较的次数每一轮都会比前一轮少一次。经过==N-1==轮
与冒泡排序的区别:
1、冒泡排序是比较相邻另个位置的两个数,而选择是顺序比较,找最大值或最小值
2、冒泡排序每一轮比较后,位置不对都需要换位,选择每一轮比较只需要和最终的一次更换位置
3、冒泡排序是通过数去找位置,选择排序是给定位置去找数
public static void select_sort(int[] arr){
int num = arr.length -1;
for (int i=0; i<num; i++){
int index = i;
for (int j=i+1; j<arr.length; j++){
if (arr[j] <= arr[index]) {
index = j;
}
}
int temp = arr[i];
arr[i] = arr[index];
arr[index] = temp;
}
}
4、插入排序(插入)
插入排序(Insertion sort)是一种简单直观且稳定的排序算法。如果有一个已经有序的数据序列,要求在这个已经排好的数据序列中插入一个数,但要求插入后此数据序列仍然有序,这个时候就要用到一种新的排序方法——插入排序法,插入排序的基本操作就是将一个数据插入到已经排好序的有序数据中,从而得到一个新的、个数加一的有序数据,算法适用于少量数据的排序,时间复杂度为O(n^2)。(扑克牌)
插入排序的基本思想是:每步将一个待排序的记录,按其关键码值的大小插入到前面已经排序的数组中的适当位置上,直到全部插入完为止。记得第二次j 为通用变量
//*****方法1
public static void insert_sort(int[] arr){
int num = arr.length;
int j ; // 用来存插入牌子的其实位置
// 从第二个位置开始
for (int i=1; i<num; i++){
// 作为临时边和 前面已经排好的进行比较
int temp = arr[i];
for (j = i-1; j>=0; j--){
if (temp < arr[j]){
arr[j+1] = arr[j];
}else {
break;
}
}
arr[j+1] = temp;
}
}
//******方法2
public static void insert_sort(int[] arr){
for (int i=1; i<arr.length; i++){
for (int j = i; j > 0 ; j--) {
if (arr[j] < arr[j-1]){
swap(arr,j,j-1);
}
}
}
}
5、希尔排序(优化插入)
插入排序的优化
思想:
1.给定一个间隔gap,按照这个间隔进行跳排
2.将间隔缩小再排一遍
3.直到间隔为1进行插排
如果一开始的间隔直接就是1, 那其实就是插入排序
public static void shell_sort(int[] arr){
for (int gap = arr.length/2; gap > 0 ; gap /=2) {
for (int i=gap; i<arr.length; i++){
for (int j = i; j > gap-1 ; j-=gap) {
if (arr[j] < arr[j-gap]) {
swap(arr, j, j - gap);
}else {
break;
}
}
}
}
}
6、归并排序(归并)
1、将序列中带排序数字分为若干组,每个数字分为一组
2、将若干个组两两合并,保证合并后的组是有序的
3、重复第二步操作直到只剩下一组,排序完成。
编程思路:分治法
1、先拆后合,每分解一轮便和一轮
2、由于是递归,拆到最小子元素就是有序的,这时开始合并
3、两边元素都是有序序列,比较两边元素放入临时数组
4、注意一边元素都放入临时数组后,记得把剩下的也放入
public static void mergeSort(int[] arr, int left, int right, int[] temp) {
// 分解
if (left < right) {
int mid = (left + right) / 2;// 中间索引
// 向左递归进行分解
mergeSort(arr, left, mid, temp);
// 向右递归进行分解
mergeSort(arr, mid + 1, right, temp);// mid + 1,中间位置的后一个位置才是右边序列的开始位置
// 每分解一轮便合并一轮
merge(arr, left, right, mid, temp);
}
}
/**
* 合并的方法
*
* @param arr 待排序的数组
* @param left 左边有序序列的初始索引
* @param right 中间索引
* @param mid 右边有序序列的初始索引
* @param temp 做中转的数组
*/
public static void merge(int[] arr, int left, int right, int mid, int[] temp) {
int i = left; // 初始化i,左边有序序列的初始索引
int j = mid + 1;// 初始化j,右边有序序列的初始索引(右边有序序列的初始位置即为中间位置的后一个位置)
int t = 0;// 指向temp数组的当前索引,初始为0
// 先把左右两边的数据(已经有序)按规则填充到temp数组
// 直到左右两边的有序序列,有一边处理完成为止
while (i <= mid && j <= right) {
// 如果左边有序序列的当前元素小于或等于右边有序序列的当前元素,就将左边的元素填充到temp数组中
if (arr[i] <= arr[j]) {
temp[t] = arr[i];
t++;// 索引后移
i++;// i后移
} else {
// 反之,将右边有序序列的当前元素填充到temp数组中
temp[t] = arr[j];
t++;// 索引后移
j++;// j后移
}
}
// 把有剩余数据的一边的元素填充到temp中
while (i <= mid) {
// 此时说明左边序列还有剩余元素
// 全部填充到temp数组
temp[t] = arr[i];
t++;
i++;
}
while (j <= right) {
// 此时说明左边序列还有剩余元素
// 全部填充到temp数组
temp[t] = arr[j];
t++;
j++;
}
// 将temp数组的元素复制到原数组
t = 0;
int tempLeft = left;
while (tempLeft <= right) {
arr[tempLeft] = temp[t];
t++;
tempLeft++;
}
}
7、计数排序(桶放)
1、计数排序是非比较排序 (空间换时间)
2、适用于特定问题,也就是对数据源有要求, 范围不易过大
3、时间复杂度 O(n+k)缺陷优化问题,: 1、稳定性(顺序)2、资源浪费
实现思想:用数值下标作为记录数,该小标的对应数组值为存在的个数
static int[] sort(int[] arr) {
int[] result = new int[arr.length]; // 最终返回的排好序的数组
int[] count = new int[10]; // 需排序数组的范围区间 这里假设 0~9 ,所以如果有1万范围,空间就要很大
for (int i = 0; i < arr.length; i++) { // 记录arr中 每个需排序数字出现的次数
count[arr[i]]++;
}
System.out.println(Arrays.toString(count));
/*
for (int i = 0, j = 0; i < count.length; i++) { // 回填 (该方法没有考虑稳定性
while (count[i]-- > 0) result[j++] = i;
}
*/
// 累加法 解决稳定性
for (int i = 1; i < count.length; i++) { // 知道每个需排序的数字,在排序好后,最后一个位置的下标
count[i] = count[i] + count[i-1];
}
System.out.println(Arrays.toString(count));
for (int i = arr.length-1; i >=0; i--) { // arr和result等长, arr重后往前的位置, 就是该值 累加后范围的重后往前放的位置
result[--count[arr[i]]] = arr[i];
}
return result;
}
8、基数排序(桶放)
本质上是一种多关键字排序
有地位优先和高位优先两种
实现思路:将数列按 个位 排序后,放到桶里,再按 十位排序后, 重新放到桶里,以此类推