数据结构6:排序

====================================================================================================

排序的概念

  • 排序:所谓排序,就是一连串数据/记录,经过一定规则或者根据某个或某些关键的大小,递增递减排列起来的操作;
  • 稳定性:假定该数列/记录序列内部存在多个相同关键字/记录,经过排序,这些关键字/记录的相对次序保持不变;这样的排序算法具有稳定性;
  • 内部排序:所有的数据元素都放在内存中进行排序;
  • 外部排序:处理的数据过多,不能全部放入内存中处理,只能放入读写较慢的外部储存器-磁盘上;(外排序通常采用的是一种"排序- 归并 "的策略。 在排序阶段,先读入能放在内存中的数据量,将其排序输出到一个临时文件,依此进行,将待排序数据组织为多个有序的临时文件。而后在归并阶段将这些临时文件组合为一个大的有序文件,也即排序结果。)

常见排序算法

在这里插入图片描述
在这里插入图片描述

插入排序

直接插入排序算法

  • 直接插入排序算法:稳定性排序;
    • 排序区间分为有序区间、无序区间 (初始以[0,1)为有序区间);
    • 每次将无序区间第一个元素和有序区间各个元素进行比较,并插入到合适位置;
    • 初始数组越有序,数组越短,插入时间效率越高;
    • 时间复杂度O(n^2),空间复杂度O(1);
  • 图解:
    在这里插入图片描述
// 直接插入排序算法:
public void insertSort(int[] arr){
	for(int bound=1;bound<arr.length;bound++){
		int v=arr[bound];
		int cur=bound-1;
		for(;cur>=0&&arr[cur]>v;cur--){
			arr[cur+1]=arr[cur]; //后挪
		}
		arr[cur+1]=v; // 插入合适位置
	}
}

希尔排序算法

  • 希尔排序/缩小增量法: 不是稳定的排序;
    • 将需要排序的数组根据gap先分成若干份区间为gap的排序区间,分别进行插入排序;然后不断更新gap的值,使整个数组更加有序,最后gap==1时,等价于插入排序,最后将数据记录统一排好序;(初始一般 gap=arr.length/2;更新:gap/2)
    • 希尔排序是当数组很长/非常无序时进行插入排序的优化算法;
    • 时间复杂度最好O(n),平均O(n^1.3),最坏O(n ^2);空间复杂度:O(1);
  • 图解:
    在这里插入图片描述
// 希尔排序
public void shellSort(int[] arr){
	int agp=arr.length/2;
	while(gap>=1){
		_shellSort(arr,gap);
		gap/=2;
	}
}
public void _shellSort(int[] arr,int gap){
	int bound=gap;
	for(;bound<arr.lemgth;bound++){
		int v=arr[bound];
		int cur=bound-gap;
		for(;cur>=0 && arr[cur]>v;cur=cur-gap){
			arr[cur+gap]=arr[cur];
		}
		arr[cur+gap]=v;
	}
}

选择排序

选择排序算法

  • 选择排序:打擂台的方式,每次将最大或最小元素放到最左边;与冒泡排序有点相似。
  • 时间复杂度为O(n^2),空间复杂度O(1);
  • 稳定性:不稳定;
    在这里插入图片描述
// 选择排序
public void selectort(int[] arr){
	for(int bound=0;bound<arr.length-1;bound++){
		for(int cur=bound+1;cur<arr.length;cur++){
			if(arr[bound]>arr[cur]){
				int t=arr[bound];
				arr[bound]=arr[cur];
				arr[cur]=t;
			}
		}
	}
}

堆排序算法

  • 堆排序:基本原理也是选择排序,只是不在使用遍历的方式查找无序区间的最大/最小的数,而是通过堆来选择无序区间的最大/最小的数;
  • 排升序建大堆,排降序建小堆;
  • 时间复杂度O(n*log(n)),空间复杂度O(1);
  • 稳定性:不稳定;
  • 图解:
    在这里插入图片描述
    //关于堆的相关操作:建堆与向下调整与向上调整可以查阅该跳转博文https://blog.csdn.net/weixin_47921628/article/details/121223625
// 堆排序
public void heapSort(int[] arr){
	//初始建大堆
	createHeap(arr);
	//交换,排序
    for(int heapSize=arr.length-1;heapSize>0;heapSize--){
        swap(arr,0,heapSize);
        shiftDown(arr,heapSize,0); //向下调整
    }
}
//建堆方法:
public createHeap(int[] arr){
	for(int index=(arr.length-1-1)*2;index>=0;index--){
		shifDown(arr,arr.length,index);
	}
}
//向下调整:
public void shifDown(int[] arr,int size,int index){
     int parent=index;
     int child=2*index+1;
     while(child<size){
         if(child+1<size&&arr[child]<arr[child+1]){
             child=child+1;
         }
         if(arr[child]>arr[parent]){
             int tmp=arr[parent];
             arr[parent]=arr[child];
             arr[child]=tmp;
         }else{
             break;
         }
         parent=child;
         child=parent*2+1;
     }
]

//交换方法:
public void swap(int[] arr,int a,int b){
	int t=arr[a];
	arr[a]=arr[b];
	arr[b]=t;
}

交换排序

冒泡排序算法

  • 冒泡排序:在无序区间,通过相邻数的比较,将最大/最小的数冒泡到无序区间的最后/最前,持续这个过程,直到数组整体有序;
  • 时间复杂度最好O(n),平均O(n^2);空间复杂度O(1);
  • 稳定性:稳定;
    在这里插入图片描述
// 冒泡排序
public void bubbleSort(int[] arr){
	for(int bound=0;bound<arr.length;bound++){
		for(int cur=arr.length-1;cur>bound;cur--){
			if(arr[cur]<arr[cur-1]){
				int t=arr[cur];
				arr[cur]=arr[cur-1];
				arr[cur-1]=t;
			}
		}
	}
}

*快速排序算法

  • 快速排序算法:以排序范围里的 最右侧/最左侧 元素作为基准值,向中间遍历,(升序为例)前面比基准值大的与后面比基准值小的元素做交换,相遇时,当前元素与基准值交换;
    1. 从待排序区间选择一个数,作为基准值(pivot);
    2. Partition方法: 遍历整个待排序区间,将比基准值小的(可以包含相等的)放到基准值的左边,将比基准值大的(可以包含相等的)放到基准值的右边;
    3. 采用分治思想,对左右两个小区间按照同样的方式处理,直到小区间的长度为1,代表已经有序,或者小区间的长度为 0,代表没有数据;
  • 时间复杂度O(nlog(n)),最坏O(n^2),空间复杂度最好/平均O(log(n)),最坏O(n);
  • 稳定性:不稳定;
  • 图解:
    在这里插入图片描述
// 快速排序
public void quickSort(int[] arr){
	//分治思想,递归方法
	_quickSort(arr,0,arr.length-1);
}
//递归方法
public void _quickSort(int[] arr,int left,int right){
	if(left>=right){
		return ;
	}
	// 从两边遍历,找的隔断区间的元素位置(左区间总比基准值小,有区间总比基准值大)
	int index=partition(arr,left,right);
	_quickSort(arr,left,index-1);
	_quickSort(arr,index+1,right);
}
//关键调整操作hoare方法,//以右侧元素为基准值
public int partition(int[] arr,int left,int right){
	int pivot=arr[right]; //以右侧元素为基准值
	int i=left;
	int j=right;
	while(i<j){
		while(i<j&&arr[i]<=pivot){i++;}
		while(i<j&&arr[j]>=pivot){j--;}
		swap(arr,i,j);
	}
	swap(arr,i,right);
	return i;
}
//交换方法:
public void swap(int[] arr,int a,int b){
	int t=arr[a];
	arr[a]=arr[b];
	arr[b]=t;
}
//以区间最左侧为基准值,挖坑方法
public int partition(int[] arr,int left,int right){
	int pivot=arr[left];
	int i=left;
	int j=right;
	while(i<j){
		while(i<j&&arr[j]>=pivot){j--;}
		arr[i]=arr[j];
		while(i<j&&arr[i]<=pivot){i++;}
		arr[j]=arr[i];
	}
	arr[i]=pivot;
	return i;
}
//前后遍历法,以左侧第一个元素为基准值
public int partition(int[] arr,int left,int right){
	int d=left+1;
	int pivot=arr[left];
	for(int i=left+1;i<right;i++){
		if(arr[i]<pivot){
			swap(arr,i,d);
			d++;
		}
	}
	swap(arr,d-1,left);
	return d-1;
}

基准值的选择

  1. 选择边上(左或者右)
  2. 随机选择
  3. 几数取中(例如三数取中):array[left], array[mid], array[right] 大小是中间的为基准值

// 非递归方法实现:借助栈(后进先出)模拟,类似先序遍历

public void quickSort(int[] arr){
	Stack<Integer> stack=new Stack<>();
    stack.push(arr.length-1);
    stack.push(0);
    while (!stack.empty()) {
        int left=stack.pop();
        int right=stack.pop();
        if (left >= right) {
            continue;
        }
        int index = partition(arr, left, right);
        stack.push(right);
        stack.push(index + 1);

        stack.push(index-1);
        stack.push(left);
    }
}

优化方法

  1. 选择基准值很重要,通常使用几数取中法
  2. partition 过程中把和基准值相等的数也选择出来
  3. 待排序区间小于一个阈值(较小的值)时,使用直接插入排序

*归并排序

  • 归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divideand Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。
  • 时间复杂度为O(nlog(n)),空间复杂度O(n);
  • 稳定性:稳定;
  • 图解
    在这里插入图片描述
//归并排序
public void mergeSort(int[] arr){
	//辅助方法,便于分治递归
	mergeSortInternal(arr,0,arr.length);
}
public void mergeSortInternal(int[] arr,int left,int right){
	if(left>=right-1){  //[left,right)此条件说明该区间只有一个元素,则返回不用归并排序;
		return;
	}
	int mid=(left+right)/2;
	//分治/分割
	mergeSortInternal(arr,left,mid);
	mergeSortInternal(arr,mid,right);
	//归并-合并两个有序数组
	merge(arr,left,mid,right);
}
//归并操作-合并两个有序数组
public void merge(int[] arr,int left,int mid,int right){
    //先设置一个空数组,存取合并之后的元素
    int[] tmp=new int[right-left];
    int l=left;
    int r=mid;
    int size=0;
    while (l < mid && r < right) {
        if (arr[l] <= arr[r]) {
            tmp[size++]=arr[l++];
        }else{
            tmp[size++]=arr[r++];
        }
    }
    while (l<mid){
        tmp[size++]=arr[l++];
    }
    while(r<right){
        tmp[size++]=arr[r++];
    }
    //合并完之后将合并的数组赋值给原数组
    while(size>=0&&right>left){
        arr[right-1]=tmp[size-1];
        size--;
        right--;
    }
}

海量数据排序处理

  • 外部排序:排序过程需要在磁盘等外部存储进行的排序(前提:内存只有 1G,需要排序的数据有 100G)因为内存中因为无法把所有数据全部放下,所以需要外部排序,而归并排序是最常用的外部排序:
    1. 先把文件切分成 200 份,每个 512 M
    2. 分别对 512 M 排序,因为内存已经可以放的下,所以任意排序方式都可以
    3. 进行 200 路归并,同时对 200 份有序文件做归并过程,最终结果就有序了

*数据与内存

  • 单位换算
    • 1B(byte)=8(bit);
    • 1KB=1024B;
    • 1MB=1024KB;
    • 1GB=1024MB;
    • 1TB=1024GB;
  • 存储单位和网速(每秒下载的字节数)的单位,不管是 B 还是 b,代表的都是 字节 Byte。(网速5KB-表示每秒下载5千字节的数据);
  • 带宽(网络线路的计量单位)的单位(每秒接收的平均比特数bps->kbps(千)->(兆比特每秒)),不管是 B 还是 b,代表的都是 比特 bit 。(比如带宽2M-表示每秒接受2百万左右的比特数据);
  • 一般 1million百万~=1M; 10亿 ~= 1G
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值