十大经典排序算法
一、直接插入排序
二、希尔排序
三、冒泡排序
四、快速排序
五、简单选择排序
六、堆排序
七、归并排序
八、基数排序
九、计数排序
十、桶排序
一、直接插入排序
【算法思想】
【代码】
public void insertSort(int[] nums){ int len = nums.length; //len=10 for(int i=1;i<len;i++){ //每次排第i个元素。 int tmp = nums[i]; //第i趟排序就是把nums[i]插进来。 int j=i; while(j>0 && nums[j-1]>tmp){ nums[j] = nums[j-1]; j--; } nums[j] = tmp; } }
时间复杂度:最好:o(n) 最坏:o(n2) 平均:o(n2)
稳定排序。
二、希尔排序
【算法思想】
我们选择增量gap=len/2,缩小增量继续以gap = gap/2的方式,这种增量选择我们可以用一个序列来表示,{n/2,(n/2)/2…1},称为增量序列。希尔排序的增量序列的选择与证明是个数学难题,我们选择的这个增量序列是比较常用的,也是希尔建议的增量,称为希尔增量,但其实这个增量序列不是最优的。
【代码】
public void shellSort(int[] nums){ int len = nums.length; for(int gap = len/2; gap>=1; gap /= 2){ //增量gap for(int i=gap; i<len ;i++){ //i++而不是i+=gap。 int tmp = nums[i]; int j=i; while(j>=gap && nums[j-gap]>tmp){ nums[j] = nums[j-gap]; j -= gap; } nums[j] = tmp; } } }
希尔排序的时间复杂度依赖于增量序列的函数,涉及到数学上尚未解决的难题。当n在某个特定范围时,希尔排序的时间复杂度约为o(n1.3)。最坏情况下为o(n2)。
三、冒泡排序
【算法思想】
【代码】
public void bubbleSort(int[] nums){ int len = nums.length; //len=10 for(int i=0;i<len-1;i++){ //只需要9趟排序。i范围:0~8 for(int j=0;j<len-i-1;j++){ //第4趟排序,i=3,已经有3个元素排好。只需排len-3个元素:0~6。j范围:0~5 if(nums[j] > nums[j+1]){ int tmp = nums[j];nums[j] = nums[j+1];nums[j+1] = tmp; //交换 } } } }
改进:
public void bubbleSort(int[] nums){ int len = nums.length; //len=10 boolean flag; for(int i=0;i<len-1;i++){ //只需要9趟排序。i范围:0~8 flag = false; for(int j=0;j<len-i-1;j++){ //第4趟排序,i=3,已经有3个元素排好。只需排len-3个元素:0~6。j范围:0~5 if(nums[j] > nums[j+1]){ int tmp = nums[j];nums[j] = nums[j+1];nums[j+1] = tmp; //交换 flag = true; //本趟有交换 } } if(flag == false) return; //若本趟没有修改,则排序完成 } }
时间复杂度:最好:o(n) 最坏:o(n2) 平均:o(n2)
四、快速排序
【思想】
【代码】
public void quickSort(int[] nums, int low, int high){ if(low >= high) return ; int pos = partition(nums, low, high); quickSort(nums, low, pos-1); quickSort(nums, pos+1, high); } public int partition(int[] nums, int low, int high){ int pivot = nums[low]; while(low < high){ while(low < high && nums[high] >= pivot){ //找到右边第一个比它小的 high--; } nums[low] = nums[high]; while(low < high && nums[low] <= pivot){ //找到左边第一个比它大的 low++; } nums[high] = nums[low]; } nums[low] = pivot; return low; }
时间复杂度:最好:o(nlogn) 最坏:o(n^2) 平均:o(nlogn)。
空间复杂度:平均:o(logn)
不稳定排序。
五、简单选择排序
【思想】
【代码】
public void selectSort(int[] nums){ int len = nums.length; //len=8 for(int i=0;i<len-1;i++){ //每一趟排好一个元素。共需要7趟。 int min = i; for(int j=i+1;j<len;j++){ //找到后面最小的元素 if(nums[j]<nums[min]){ min = j; } } if(min != i){ int tmp=nums[i];nums[i]=nums[min];nums[min]=tmp; } } }
时间复杂度:最好: o(n^2) 最坏:o(n^2) 平均: o(n^2) 。
空间复杂度:o(1)
不稳定排序。
六、堆排序
【思想】
【代码】
public void heapSort(int[] nums){ int len = nums.length; buildMaxHeap(nums); //建初堆 for(int i=len-1; i>0; i--){ int tmp=nums[0]; //交换 nums[0]=nums[i]; nums[i]=tmp; adjustDown(nums, 0, i); //筛第一个 } } public void buildMaxHeap(int[] nums){ int len = nums.length; for(int i=(len-1)/2; i>=0; i--){ adjustDown(nums, i, len); } } public void adjustDown(int[] nums, int k, int len){ //筛nums[k] int tmp = nums[k]; //暂存nums[k] for(int i=2*k+1; i<len ;i = 2*i+1){ //让i顺着左子树遍历 if(i+1<len && nums[i]<nums[i+1]){ //用i记录左右孩子中较大的一个。 i = i+1; } if(nums[i] <= tmp){ //左右孩子都比tmp小,则找到了tmp的最终存放位置。 break; }else{ nums[k] = nums[i]; //把左右孩子中的较大值放到k这个位置。 k = i; //现在i这个地方空出来了。k始终记录空着的位置。 } } nums[k] = tmp; }
建堆:o(n);n-1次向下调整,每次调整时间为o(h)。
故最好、最坏、平均情况下: 时间复杂度为o(nlogn) 。
空间复杂度:o(1)
不稳定排序。
七、归并排序
【思想】
【代码】
public static void mergeSort(int[] nums, int low, int high){ if(low >= high) return; int mid = (low+high)/2; mergeSort(nums, low, mid); mergeSort(nums, mid+1, high); merge(nums, low , mid, high); } public static void merge(int[] nums, int low, int mid, int high){ int[] tmp = new int[high-low+1]; //暂时排放到tmp中 int i=low; int j=mid+1; int k=0; while(i<=mid && j<=high){ if(nums[i]<=nums[j]){ tmp[k++] = nums[i++]; }else{ tmp[k++] = nums[j++]; } } while(i<=mid){ tmp[k++] = nums[i++]; } while(j<=high){ tmp[k++] = nums[j++]; } for(i=low,k=0;i<=high;i++){ //从tmp中放回原数组 nums[i]=tmp[k++]; } }
每一趟归并的时间是o(n),共进行logn趟。所以时间复杂度为o(nlogn)。
空间复杂度为o(n)
稳定排序。
八、基数排序
【思想】
【代码】
public void radixSort(int[] nums){ //找出最大的数 int max = nums[0]; for(int num : nums){ if(num > max){ max = num; } } //看看最大的数有几位 int n=0; //记录做大的位数 while(max!=0){ max /= 10; n++; } //创建10个桶,每个桶是一个队列 List<Queue<Integer>> bucket = new LinkedList<Queue<Integer>>(); for(int key=0; key<10; key++){ bucket.add(new LinkedList<Integer>()); } //从低位到高位进行分配和收集 for(int i=0;i<n;i++){ //分配 for(int num:nums){ int key = (num/(int)(Math.pow(10, i)))%10; //得到num的第i位。 bucket.get(key).offer(num); //将num放入到对应的桶中。 } //收集 int k=0; for(int j=0;j<10;j++){ while(!bucket.get(j).isEmpty()){ nums[k++] = bucket.get(j).poll(); } } } }
共进行位数趟分配和收集。每一趟分配的时间为o(n),每一趟收集的时间是o(n+桶数)。所以,时间复杂度:o(位数*(n+n+桶数)) = o(位数*(n+桶数))
空间复杂度:o(n+桶数)。
稳定排序。
注:复杂度这里很多地方分析的不一样。欢迎讨论。
九、计数排序
【思想】
比如:3 5 4 4 6 8 10 8 共8个元素。
最小值为3,最大值为10。需要建立的桶长度为8。分别存储3的个数,4的个数,5的个数,…,10的个数。
最后遍历一遍桶,最后得到排序结果。3的个数有几个就输出几次,4的个数有几个就输出几次,…,10的个数有几个就输出几次。
即:
- 扫描一下整个数组array,获取最小值 min 和最大值 max。
- 创建新的桶(就是一个数组),长度为 max - min + 1
- 桶中 index 的元素记录的值是数组中某元素出现的次数。即:第一个桶存放元素min的个数,第二个桶存放min+1的个数…
- 最后输出目标整数序列,即遍历桶,输出相应元素以及对应的个数
【代码】
public void countingSort(int[] nums){ //找到最大最小数,确认桶的范围 int max,min; max = min = nums[0]; for(int num : nums){ if(num > max) max = num; if(num < min) min = num; } //创建桶,并初试化为0 int[] bucket = new int[max-min+1]; //初始默认为0. //利用桶,对数组元素进行计数 for(int num : nums){ bucket[num-min]++; } //输出 int j=0; for(int i=0; i<bucket.length; i++){ while(bucket[i] != 0){ nums[j++] = min+i; bucket[i]--; } } }
时间复杂度:o((n+桶数)),主要花在输出上面。
空间复杂度:o(桶数)。
稳定排序。
注:复杂度这里很多地方分析的不一样。欢迎讨论。
十、桶排序
【思想】
【代码】
public void bucketSort(int[] nums, int bucketSize){ //bucketSize:每个桶的宽度 //找到nums数组中数的范围,以便确定桶的数量 int max,min; max = min = nums[0]; for(int num: nums){ if(num > max) max = num; if(num < min) min = num; } int n = (max-min)/bucketSize + 1; //桶数量为n //创建n个桶 List<LinkedList<Integer>> bucket = new LinkedList<LinkedList<Integer>>(); for(int i=0; i<n; i++){ bucket.add(new LinkedList<Integer>()); } //把数组元素放到桶中 for(int num :nums){ bucket.get((num-min)/bucketSize).add(num); } //对每个桶进行排序 for(int i=0;i<n;i++){ if(bucket.get(i).size()>1){ insertSort(bucket.get(i)); } } //输出 int j=0; for(int i=0;i<n;i++){ while(!bucket.get(i).isEmpty()){ nums[j++] = bucket.get(i).remove(0); } } } public void insertSort(LinkedList<Integer> list){ //链表插入排序 int len = list.size(); for(int i=1;i<len; i++){ int e = list.remove(i); int j=0; while(j<len-1 && list.get(j)<e){ j++; } list.add(j, e); } }
时间复杂度:最好:每个数分到一个桶中,不需要排序,时间主要花在输出上,输出时间为o(n+桶数);最差:所有数分到一个桶中,时间主要花在排序上,为:o(n^2),因为这里是插入排序。平均时间复杂度:不考虑排序,或者认为每个桶元素不多的情况下,时间主要花在输出上,为o(n+桶数)
空间复杂度:o(n+桶数)。
稳定排序。
注:复杂度这里很多地方分析的不一样。欢迎讨论。
- 点赞
- 收藏
- 分享
- 文章举报
- Python版十大经典排序算法解析
- 十大经典排序算法(含C++详细代码)下篇
- 用JavaScript实现十大经典排序算法--冒泡排序
- 十大经典排序算法
- 十大经典排序算法最强总结详细讲解带案例分析
- 十大经典排序算法总结(JavaScript描述)
- 先码后看 十大经典排序算法最强总结(含Java代码实现) 侵立删
- JavaScript十大经典排序算法
- 十大经典排序算法(含C++详细代码)上篇
- 用JavaScript实现十大经典排序算法--选择排序
- 十大经典排序算法的 JS
- 十大经典排序算法的JS版
- 十大经典排序算法总结(JavaScript描述)
- 十大经典排序算法总结(JavaScript描述)
- 用JavaScript实现十大经典排序算法
- 十大经典排序算法
- 【归纳整理】十大经典排序算法的简单C++实现
- 作为程序员,你一定要知道的十大经典排序算法!(详细解析)
- 十大经典排序算法总结(JavaScript描述)(转)
- JavaScript 十大经典排序算法