一.稳定性
二.七大排序的思路与其基本实现
1.直接插入排序
原理:整个区间被分为 (1)有序区间 (2)无序区间
每次选择无序区间的第一个元素, 在有序区间内选择合适的位置插入即可
代码实现:
public static void insertSort(int[] array){
for(int i=1;i<array.length;i++){
int key=array[i];
int j;
for(j=i-1;j>=0;j--){ //在有序区间中找合适的插入位置
if(array[j]<=key){
break;
}else{
array[j+1]=array[j];
}
}
array[j+1]=key;
}
}
2.希尔排序法
又称缩小增量法。
希尔排序法的基本思想是:先选定一个整数,把待排序文件中所有记录按这个整数分成很多个组,并对每一组内的记录进行直接插入排序。然后重复上述分组和排序的工作。当分组的距离为 1 时,所有记录再统一排好序。
1.希尔排序是对直接插入排序的优化。
2.当gap > 1时都是预排序,目的是让数组更接近于有序。当gap == 1时,数组已经接近有序的了,这样就会很快。这样整体而言,可以达到优化的效果。
代码实现:
public static void shellSort(int[] array){
int gap=array.length;
while(true){
gap=(gap/3)+1; //gap = gap / 2;
insertSortWithGap(array,gap);
if(gap==1){
break;
}
}
}
private static void insertSortWithGap(int[] array, int gap) {
for(int i=gap;i<array.length;i++){
int key=array[i];
int j;
for(j=i-gap;j>=0;j-=gap){
if(array[j]<=key){
break;
}else{
array[j+gap]=array[j];
}
}
array[j+gap]=key;
}
}
3.直接选择排序
原理:每一次从无序区间选出最大(或最小)的一个元素,存放在无序区间的最后(或最前),直到全部待排序的数据元素排完。
代码实现:
public static void selectSort(int[] array){
for(int i=0;i<array.length-1;i++){ //循环需要进行array.length-1次
int maxIndex=0;
for(int j=1;j<array.length-i;j++){
if(array[j]>array[maxIndex]){
maxIndex=j;
}
}
swap(array,maxIndex,array.length-1-i);
//每次将最大数放到无序区间的最后
}
}
private static void swap(int[] array, int i, int j) {
int t=array[i];
array[i]=array[j];
array[j]=t;
}
4.冒泡排序
原理:在无序区间,通过相邻数的比较,将最大的数冒泡到无序区间的最后,持续这个过程,直到数组整体有序
代码实现:
public static void bubbleSort(int[] array){
for(int i=0;i<array.length-1;i++){ //总共排n-1趟
boolean isSort=true;
for(int j=0;j<array.length-1-i;j++){ //到倒数第二个数
if(array[j]>array[j+1]){
swap(array,j,j+1);
isSort=false; //只要发生了交换就置为false
} //没有发生一次交换说明数组已经有序
}
if(isSort){ //数组已经有序
return;
}
}
}
private static void swap(int[] array, int i, int j) {
int t=array[i];
array[i]=array[j];
array[j]=t;
}
5.堆排序
原理:基本原理也是选择排序,只是不在使用遍历的方式查找无序区间的最大的数,而是通过堆来选择无序区间的最大的数。
注意:排升序要建大堆;排降序要建小堆。
代码实现:
public static void creatHeapBig(int[] array){
for(int i=(array.length-2)/2;i>=0;i--){
shiftDownBig(array,i,array.length);
}
}
//向下调整为大堆
public static void shiftDownBig(int[] array,int index,int size){
int left=2*index+1;
while(left<size){
int right=2*index+2;
int max=left;
if(right<size){
if(array[right]>array[max]){
max=right;
}
}
if(array[index]<array[max]){
swap(array,index,max);
}else{
break;
}
index = max;
left = 2 * index + 1;
}
}
//堆排序
public static void heapSort(int[] array){
creatHeapBig(array); //先创建一个大堆
for(int i=0;i<array.length-1;i++){ //排序array.length-1次
swap(array,0,array.length-1-i);
shiftDownBig(array,0,array.length-1-i);
}
}
private static void swap(int[] array, int i, int j) {
int t=array[i];
array[i]=array[j];
array[j]=t;
}
6.快速排序
原理:
- 1.从待排序区间选择-一个数, 作为基准值(pivot);
- 2.Partition:遍历整个待排序区间,将比基准值小的(可以包含相等的)放到基准值的左边,将比基准值大的
(可以包含相等的)放到基准值的右边; - 3.采用分治思想,对左右两个小区间按照同样的方式处理,直到小区间的长度为 1,代表已经有序,或者小区间的长度为 0,代表没有数据。
代码实现:
public static void quickSort(int[] array) {
quickSortInter(array, 0, array.length - 1);
}
private static void quickSortInter(int[] array, int left, int right) {
if(left>=right){
return; //排序区间的长度<=1则不需要排序了,退出就好
}
int pivotIndex=partition(array,left,right);
//此时已经根据基准值将数组划分为左右区间
quickSortInter(array,left,pivotIndex-1); //对左区间进行快排
quickSortInter(array,pivotIndex+1,right); //对右区间进行快排
}
/*2.挖坑法
在区间[begin,end]中进行,选取基准值array[left]
从最右边开始进行,如果值大于基准值,end一直--
找到小于基准值的元素,将它的值赋给基准值array[left]
然后从左边开始找,如果值小于基准值,begin一直++
找到大于基准值的停下,将它的值付给刚刚的array[end]
直到begin==end退出循环
将array[left]的值赋给array[begin],然后返回下标
*/
public static int partition(int[] array,int left,int right){
int begin=left;
int end=right;
int pivot=array[left];
while(begin<end){
while(begin<end&&array[end]>=pivot){
end--;
}
array[begin]=array[end];
while(begin<end&&array[begin]<=pivot){
begin++;
}
array[end]=array[begin];
}
array[begin]=pivot;
return begin; //基准值的下标
}
private static void swap(int[] array, int i, int j) {
int t=array[i];
array[i]=array[j];
array[j]=t;
}
基准值的选择:
- 1.选择边上(左或者右)
2.随机选择
3.几数取中(例如三数取中) : array[left], array[mid], array[right]值是中间的为基准值
7.归并排序
原理:归并排序(MERGE-SORT) 是建立在归并操作.上的一种有效的排序算法,该算法是采用分治法(Divide andConquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的列;即先使每个子序列有序,再使子列段间有序。若将两个有序表合并成一个有序表, 称为二路归并。
代码实现:
public static void mergeSort(int[] array){
mergeSortInter(array,0,array.length);//排序区间范围[0,array.length)
}
//排序区间范围[low,high)
private static void mergeSortInter(int[] array, int low, int high) {
if(low>=high-1){
return;
}
int mid=(low+high)/2;
mergeSortInter(array,low,mid); //分解左区间
mergeSortInter(array,mid,high); //分解右区间
merge(array,low,mid,high); //左右有序区间排序后做归并
}
private static void merge(int[] array, int low, int mid, int high) {
int length=high-low; //归并的区间长度
int[] extra=new int[length];
int k=0;
int i=low,j=mid;
while(i<mid&&j<high){
if(array[i]<=array[j]){
extra[k++]=array[i++];
}else{
extra[k++]=array[j++];
}
}
while(i<mid){ //若这个数组还有剩余元素则直接拷贝
extra[k++]=array[i++];
}
while(j<high){ //若这个数组还有剩余元素则直接拷贝
extra[k++]=array[j++];
}
for(int m=0;m<length;m++){ //将extra数组内容拷贝到[low,high)区间
array[low+m]=extra[m];
}
}
8.二分查找
前提:一组有序的序列
思想:每次都是以序列的中间位置的数来与待查找的关键字进行比较,每次缩小一半的查找范围,在相同的区间做同样的步骤,直到匹配成功。
代码实现:
public int getIndex(int[] arr, int num) {
int left = 0;
int right = arr.length - 1;
int mid;
while (left <= right) {
mid = (left + right)/2
if (arr[mid] > num) {
right = mid - 1;
} else if (arr[mid] < num) {
left = mid + 1;
} else {
return mid; //返回要查找的元素的所在下标
}
}
// 当查找范围的最左侧和最右侧重叠后还没有找到元素,则返回-1表示没有找到
return -1;
}
三.总结
四.扩展
海量数据的排序问题:
外部排序:排序过程需要在磁盘等外部存储设备中进行的排序
前提:内存只有 1G,需要排序的数据有 100G
----------因为内存中无法一次性把所有数据全部存储下,所以需要外部排序,而归并排序是最常用的外部排序
- 先把文件切分成 200 份,每一份占 512 M
- 分别对 512 M 数据进行排序,因为内存已经可以放的下 512 M 数据,所以任意排序方式都可以
- 对 200 次的结果(每次都已经有序了)进行 200 路归并,同时对 200 份有序文件做归并过程,最终结果就有序了。