排序
常见的排序列表
测试代码及其他封装算法
//Test
public static void main(String[] args) {
int[] testArry = {2, 3, 5, 1, 6, 4, 7, 9, 10, 6, 25, 48, 59, 63, 55, 88, 46, 98, 37, 12, 16, 17, 23, 34};
int[] rightResult = {1, 2, 3, 4, 5, 6, 6, 7, 9, 10, 12, 16, 17, 23, 25, 34, 37, 46, 48, 55, 59, 63, 88, 98};//参照用
long t1 =System.currentTimeMillis();
boolean flag=true;
for (int i = 0; i < 100; i++) {
int[] testLargeArray=generateRandomArray();
int[] rightLargeArray=new int[9999];
System.arraycopy(testLargeArray,0,rightLargeArray,0, rightLargeArray.length);
shell(testLargeArray);
Arrays.sort(rightLargeArray);
if(!(Arrays.equals(testLargeArray,rightLargeArray))){
flag=false;
break;
}
}
long t2 =System.currentTimeMillis();
print(testArry);
System.out.println();
System.out.println("排序结果为: "+flag);
System.out.println("排序所用时间为: "+(t2-t1));
}
//随机生成较大的数组
public static int[] generateRandomArray(){
Random random = new Random();
int[] testLargeArray=new int[9999];
for (int i = 0; i < 9999; i++) {
testLargeArray[i] = random.nextInt(9999);
}
return testLargeArray;
}
//打印
public static void print(int[] arr){
for (int i = 0;i<arr.length; i++ ){
System.out.print(arr[i]+" ");
}
}
//交换
public static void swap(int[] arr, int i ,int j){
int temp;
temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
选择排序
最简单,效率差,不稳定
//选择排序 测试用时:2723ms
public static void xuanze(int[] arr){
for (int i = 0; i < arr.length-1 ; i++) {
int minPos = i;
for(int j = i+1; j < arr.length; j++ ){
minPos = arr[minPos] > arr[j] ? j : minPos;
}
int temp;
temp = arr[i];
arr[i] = arr[minPos];
arr[minPos] = temp;
}
}
冒泡排序
优化版本:加一个flag标志,若某次循环时已排好,跳出循环
//冒泡排序 9694ms
public void maopao(int[] arr) {
for (int i = arr.length; i > 0; i--) {
boolean flag = true;
for (int j = 0; j < i - 1; j++) {
if (arr[j] > arr[j + 1]) {
swap(arr, j, j + 1);
flag = false;
}
}
if (flag) {//第一次这里犯了一个低级错误:flag=true,Orz)
break;
}
}
}
插入排序
//插入排序 1694ms
public static void charu(int[] arr){
for (int i = 1; i < arr.length; i++) {
for (int j = i; j > 0 && arr[j]<arr[j-1]; j--) {
swap(arr,j,j-1);
}
}
}
希尔排序
插入排序的优化算法
//希尔排序 249ms
public static void shell(int[] arr){
for (int gap = arr.length/2; gap >= 1; gap /= 2) {
for (int i = gap; i < arr.length; i++) {
for (int j = i; j >= gap && arr[j]<arr[j-gap]; j-=gap) {
swap(arr,j,j-gap);
}
}
}
}
归并排序
抽象的理解为二叉树的后序遍历
//归并排序 299
public static void merge(int[] arr, int left, int right) {
int[] temp = new int[right - left + 1];
int mid = left + (right - left) / 2;
int i = left; //找了好久的bug,一开始写成了i=0,报错数组越界
int j = mid + 1;
int m = 0;
if (left == right) return;
merge(arr, left, mid);
merge(arr, mid + 1, right);
while (i <= mid && j <= right) {
temp[m++] = arr[i] > arr[j] ? arr[j++] : arr[i++];
}
while (i <= mid) temp[m++] = arr[i++];
while (j <= right) temp[m++] = arr[j++];
for (int k = 0; k < temp.length; k++) {
arr[left + k] = temp[k];
}
}
快速排序
抽象的理解为二叉树的后序遍历
//快排 206ms
public static void quickSort(int[] arr, int left, int right) {
if (left < right) {
int index =getIIndex(arr,left,right);
quickSort(arr, left, index - 1);
quickSort(arr, index + 1, right);
}
}
public static int getIIndex(int[] arr,int left, int right){
int low = left;
int high = right;
int temp = arr[low];
while (low < high) {
while (arr[high] >= temp && low < high) high--;
arr[low] = arr[high];
while (arr[low] <= temp && low < high) low++;
arr[high] = arr[low];
}
arr[low] = temp;
return low;
}
计数排序
//计数排序 78ms
//适合量大范围小,需要提前知道范围
public static int[] jishu(int[] arr){
int[] result = new int[arr.length];
int[] count = new int[10];
for (int i = 0; i < arr.length; i++) {
count[arr[i]]++;
}
/*
这里提前已经分配好了result数组的大小,不需要考虑其长度,只需专注于count数组的长度即可
这里i为count数组的下标,j为count数组的下标
*/
for (int i=0, j =0;i<count.length;i++) {
while (count[i]-->0){
result[j++]=i;
}
}
return result;
}
基数排序
一开始bug原因:对–count理解错误
//基数排序 165ms
//这里以待排序数有4位数举例
public static int[] baseSort(int[] arr){
int[] result = new int[arr.length];
int[] count = new int[10];//必是10
for (int i = 0; i < 4; i++) {
int div = (int)(Math.pow(10,i));
for (int j = 0; j < arr.length; j++) {
count[arr[j]/div%10]++;
}
//处理数组得到每段数的最后位置
for (int m = 1; m < count.length; m++) {
count[m] = count[m] +count[m-1];
}
//倒序扫描数组,使排序算法稳定,同样适用于上面的计数排序
//bug记录:一开始数组下标忘记减1了
for (int n = arr.length -1; n >= 0 ; n--) {
result[--count[arr[n]/div%10]] = arr[n];
}
//一开始把下面这两句话漏了
System.arraycopy(result,0,arr,0,arr.length);
Arrays.fill(count,0);
}
return result;
}
堆排序
大体可分为两步,①初始化建堆和②排序重建堆
第一大步的时间复杂度为O(n)第二大步的时间复杂度约为log(n!)和nlogn是同阶函数,这也是它的时间复杂度。
两个问题:1. 调整堆后,若调整的父节点的子节点还有子节点,则子节点也应该调 虽然是从下至上,从左至右的顺序调整(也可简单理解为从后向前调整),但任然无法保证从原来父节点换下来的“小”节点比子节点小。
2. ②步中无需再像①步一样从第一个非叶子节点建堆,只需要从根节点调整即可
上代码:
//堆排序 218ms
public static void heapSort(int[] arr){
for (int i = arr.length/2 - 1; i >=0 ; i--) {
adjustHeap(arr,i,arr.length);
}
for (int j = arr.length-1; j > 0 ; j--) {
swap(arr,0,j);
adjustHeap(arr,0,j);
}
}
public static void adjustHeap(int[] arr,int i,int length){
int temp = arr[i];
for (int k = 2*i+1; k < length; k=2*k+1) {//还有子节点的话,继续调整
//重点:这里前后的顺序千万不能反,否则会报错数组越界!!!!!
if(k+1<length&&arr[k]<arr[k+1])k++;//这里一开始if括号里忘记加k+1<length.
if(arr[k]>temp){//这里应该时temp不是a[i]
arr[i]=arr[k];
i=k;
} else {
break;
}
}
arr[i]=temp;//这里一开始写成了了a[k]报错,原因:k的作用域只在for循环内
}
最终完整排序算法项目截图: