一、冒泡排序 谁大谁上,每一轮都把最大的顶到天花板,效率太低O(n²)。
就是从数组中依次取出元素与数组剩下的元素相比较,当前面的元素比后面的元素大就交换。
public class demo0 {
public static void main(String[] args) {
int[] arr = {1,99,24,63,4,34,35,6,32,3};
Bubbling(arr);
System.out.println(Arrays.toString(arr));//打印结果
}
public static void Bubbling(int[] arr){
//第一层循环控制要比的数,
for (int i = 0; i < arr.length-1; i++) {
//每循环一次就会有一个排好的数不用比较,比较数就减i
for (int j = 0; j < arr.length-1-i; j++) {
//当前面的数大于后面的数就交换
if (arr[j] > arr[j+1]){
int temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
}
}
二、选择排序
效率较低,但经常用它内部的循环方式来找最大值和最小值 O(n²)。
public class demo1 {
public static void main(String[] args) {
int[] arr = {1,99,24,63,4,34,35,6,32,3};
selectSort(arr);//排序
System.out.println(Arrays.toString(arr));//打印结果
}
//思路:
//选出一个最小值与第一个元素交换,选出第二小值与第二个元素交换。。。。
public static void selectSort(int[] arr){
for (int j = 0; j < arr.length; j++) {
int min = j; //最小值下标
//遍历数组获取最小值
for (int i = j+1; i < arr.length; i++) {
if (arr[i] < arr[min]){
min = i;
}
}
//将最小值与第一个元素相换
int temp = arr[j];
arr[j] = arr[min];
arr[min] = temp;
}
}
}
三、插入排序 平均效率低,但是在序列基本有序时,它很快O(n²)。 思路:把小的往左边移动 首先,依次取出 1-arr.length的元素与 数组0至i-1相比较,谁小谁就往左边移动。 将取出的元素保存至temp,好进行比较和赋值。 循环将1-arr.length的元素temp与 数组0至i-1相比较,当0至i-1小于等于temp不进行交换,但大于temp时进行交换。
public class demo2 {
public static void main(String[] args) {
int[] arr = {-1, 0, 1, 10, 2, 9, 3, 8, 4, 7, 5, 6, -1, 0, 1};
insertSort1(arr);
System.out.println(Arrays.toString(arr));
}
public static void insertSort1(int[] arr) {
// 首先,依次取出 1-arr.length的元素与 数组0-i相比较
for (int i = 1; i < arr.length; i++) {
int temp = arr[i]; //保存取出的元素,好进行比较和赋值
int j = i - 1;
// j要大于等于0,j小于0,数组下标越界,j就是从 i-1遍历到0,temp就是要和i-1至0相比较
// 交换位置的另一个条件就是大于temp,小于等于temp就不叫唤位置。
while (j >= 0 && arr[j] > temp) {
arr[j + 1] = arr[j];
j--;
}
//当0至i-1小于等于temp不进行交换,arr[j + 1] = temp;,
//当0至i-1大于temp,这里相当于arr[j] = temp;
arr[j + 1] = temp; //不交换位置
}
}
public static void insertSort2(int[] arr) {
// 首先,依次取出 1-arr.length的元素与 数组0至i-1相比较
for (int i = 1; i < arr.length; i++) {
int tmp = arr[i];
boolean isInsert=false;
for (int j = i-1; j >=0 ; j--) {
//如果遇到比自己大的,大的值就后移
if (tmp < arr[j]){
arr[j+1]=arr[j];
}else {//如果遇到比自己小的,就在后方插入
arr[j+1]=tmp;
isInsert=true;
break;
}
}
//如果一直没插入,就插入在首位
if (!isInsert){//isInsert==false
arr[0]=tmp;
}
}
}
}
四、希尔排序
分组插入排序,最开始以间隔为长度一半分组,每组插入排序,然后间隔再一半再分组,直到间隔为1, 里面的插入相当于把普通插入排序1改成间隔,但是第二个for里面不改,是i++ 不是把每个组拿出来,往自己组做插入排序 5往9插,4往8插,3往7插,2往6插,1往5和9插,来实现每间隔组插入排序
public class demo3 {
public static void main(String[] args) {
int[] arr = {9,9,3,4,6,7,3,8};
shellSort(arr); //排序
System.out.println(Arrays.toString(arr));//打印结果
}
public static void shellSort(int[] arr){
//不断的缩小增量
for (int interval=arr.length/2; interval > 0; interval/=2) {
//增量为interval的 插入排序
for (int i = interval; i < arr.length; i++) {
int temp = arr[i];
int j = i-interval;
while (j>=0 && temp<arr[j]){
arr[j+interval] = arr[j];
j-=interval;
}
arr[j+interval]=temp;
}
}
}
}
五、快速排序
是软件工业中最常见的常规排序法,其双向指针扫描和分区算法是核心,往往用于解决类似问题,特别地partition算法用来划分不同性质的元素,partition->selectK,也用于著名的top问题。 O(NlgN),但是如果基准数(主元) 不是中位数的话,特别地如果每次主元都在数组区间的一侧,复杂度将退化为N²。 快速排序算法的优化: 三点取中法:在数组中取左中右三个值,取中间值作为基准数 绝对中值法: 小数据量用插入排序:当待排序数组小于等于8的时候,使用插入排序
一遍单向扫描法
/** 基准数 == 主元
* 一遍扫描法的思路是,用两个指针将数组划分为三个区间(小于基准数区间 sp-> 未知区间 <-bigger 大于基准数区间)
*
* 使用两个指针,一个扫描指针(sp),一个尾指针(bigger),
* 扫描指针确认元素小于基准数,当元素大于基准数就将该元素与尾指针的元素交换。
* 扫描指针左边的元素确认小于基准数,尾指针右边的元素确认大于基准数。
*
* 当结束后,基准数将数组分为两个部分,基准数也归位。
* 在通过相同的排序思想,分别对基准数两边的区域进行快速排序,左边对[left, 基准数-1]子数组排序,右边则是[基准数+1, right]子数组排序。
* 利用递归算法,对分治后的子数组进行排序。
*
* 排完序后,不用合并数组,因为数组右侧的数就是比数组左侧的要大。
*/
public class _5快速排序 {
public static void main(String[] args) {
int[] arr = {12,2,31,4,5,6,7,8,9,10,23,4,54,32,45};
QuickSort(arr,0,arr.length-1); //p 扫描指针,q 尾指针
System.out.println(Arrays.toString(arr));
}
//将大于基准数的元素与尾指针的元素交换
static void swap(int arr[],int sp,int bigger){
int temp = arr[sp];
arr[sp] = arr[bigger];
arr[bigger] = temp;
}
//快速排序,注重分区思想
static void QuickSort(int arr[], int p, int q){
//当p指针小于q指针就进行排序,当p指针大于等于q指针就结束排序
if(p<q){
int base = partition2(arr,p, q);
QuickSort(arr, p,base -1); // 基准数左侧子数组
QuickSort(arr,base +1, q); //基准数右侧子数组
}
}
static int partition(int arr[],int p,int q){
int base = p; //直接取第一个数作为基准数
int sp = p+1; //扫描指针
int bigger=q; //尾指针
while (sp <= bigger){ //扫描指针小于等于尾指针就进行扫描
if(arr[sp] <= arr[p]){ //扫描指针的元素小于等于基准数,就不变,将扫描指针指向下一位
sp++;
}else{ //扫描指针的元素大于基准数,交换 扫描指针元素 和 尾指针元素,并将尾指针向前移一位
swap(arr, sp, bigger);
bigger--;
}
}
//将最后尾指针上的值与基准数做交换,此时基准数就找到了临界点的位置k,位置k两边的数组都比当前位置k上的基准值或都更小或都更大。
swap(arr, base , bigger);
return bigger; //将基准数返回,进行子数组区域划分
}
}
//双向扫描法
/**
* 双向扫描的思路是,头尾指针往中间扫描,从左找到大于主元的元素,从右找到小于等于主元的元素二者交换,继续扫描,直到左侧无大元素,右侧无小元素.
*/
static int partition2(int arr[], int _left, int _right){
int base = _left; //取第一个数作为就基准数
int left = _left+1; //头指针
int right = _right; //尾指针
while(left <= right){ //当头指针小于等于尾指针进行扫描
//当小于等于尾指针 并且头指针的元素小于等于基准数,就将头指针指向下一位
//要是头指针大于尾指针,或者头指针元素大于基准数,就退出循环,交换头指针和尾指针元素
while(left<=right && arr[left]<=arr[base]) {
left++;
}
//当小于等于尾指针 并且尾指针元素大于基准数,就将尾指针指向前一位
//要是头指针大于尾指针,或者尾指针元素小于等于基准数,就退出循环,交换头指针和尾指针元素
while(left<=right && arr[right]>arr[base]){
right--;
}
//第一个循环,头指针会找到一个大于基准数的元素,并退出循环,进入第二循环,寻找小于基准数的元素,找到后退出循环,交换两个元素。
//判断头指针,是否小于尾指针,小于则交换。
if(left < right){
swap(arr,right,left);
}
}
//将基准数与尾指针元素交换
swap(arr,base,right);
return right; //返回基准数。
}
六、归并排序, 归并重视子问题的解的合并,空间换时间——逆序对数 分解:将n个元秦分成各含n/2个元素的子序列; 解决:对两个子序列递归地排序; 合并:合并两个已排序的子序列以得到排序结果
public class _6归并排序 {
public static void main(String[] args) {
int[] arr = {12,2,31,4,5,6,7,8,9,10,23,4,54,32,45};
int[] arrCopy = arr.clone(); //arrCopy为拷贝数组,arr为原数组
MergeSort(arrCopy, arr, 0, arr.length-1);
System.out.println(Arrays.toString(arr));
}
//归并排序:入口
static void MergeSort(int[] arrCopy,int[] arr,int start,int end){ //arrCopy 拷贝数组,arr 原数组,start 头指针,end 尾指针
//arrCopy为拷贝,arr为要排序数组,nowArr是一个新的空数组
int[] nowArr = new int[arr.length];
if(start == end) { //当头指针等于尾指针,将
arr[start] = arrCopy[start];
}else{
//中间下标,将arrCopy[s..e] 平分为arrCopy[s..m] 和arrCopy[m+1..e]
int min = (start + end) / 2;
// 分区:使用min,将arrCopy不断划分,成arrCopy[start ..min],arrCopy[min+1 ..end]
MergeSort(arrCopy, nowArr, start, min); //递归地将arrCopy[s..m]归并为有序的nowArr[s..e]
MergeSort(arrCopy, nowArr, min+1, end); // 递归地将arrCopy[m+1.. e]归并为有序的nowArr[m+1..e]
// 归并:将nowArr[s.. m] 和nowArr[m+1.. e]归并到 arr[s..e ]
Merge(nowArr, arr, start, min, end);
}
}
//归并
static void Merge(int[] nowArr,int[] arr, int start,int min,int end){
//将nowArr左右半有序。归并到arr
int x, y; //x为arr合并位置下标,y为右边最小元素下标,start为左边最小元素下标
//当头指针 小于等于中间下标 且右边最小元素下标 小于等于尾指针,开始循环
for(x=start,y=min+1; start<=min&&y<=end; ++x){
//当左边最小元素下标上的值 小于右边最小元素下标上的值,就将左边最小元素下标上的值赋到arr数组上,并将指针指向下一位。
//否则就是右边最小元素下标上的值 大于等于左边最小元素下标上的值,就将右边最小元素下标上的值赋到arr数组上,并将指针指向下一位。
if(nowArr[start] < nowArr[y]){
arr[x] = nowArr[start++];
}else{
arr[x] = nowArr[y++];
}
}
//开始头指针 小于等于中间下标
while(start <= min){
arr[x++] = nowArr[start++];
}
while (y <= end){
arr[x++] = nowArr[y++];
}
}
}
七、堆排序
1、堆的概念: 二叉树 --满足二叉堆的性质-> 二叉堆 二叉堆是完全二叉树或者是近似完全二叉树。 完全二叉树:每一个父节点下面都有两个子节点。 近似完全二叉树:左侧是排满的,右边有空缺。 二叉堆满足二个特性: 1.父结点的键值总是大于或等于(小于或等于)任何一个子节点的键值。 2.每个结点的左子树和右子树都是一个二叉堆(都是最大堆或最小堆)。 任意节点的值都大于其子节点的值—大顶堆 任意节点的值都小于其子节点的值—小顶堆 堆排序 1.堆化,反向调整使得每个子树都是大顶或者小顶堆。 2.按序输出元素:把堆顶和最末元素对调,然后调整堆顶元素。
public class _7堆排序 {
public static void main(String[] args) {
//数组第一位预留,不参与排序
int arr[] = new int[]{0, 49, 38, 65, 97, 76, 13, 27, 49};
HeapSort(arr);
System.out.println(Arrays.toString(arr));
}
//堆化
static void HeapSort(int[] H){
//H.length-1相当于H的length
for(int i=(H.length-1)/2; i>0; i--){//把H.r[1.. H.length]建成大顶堆
HeapAdjust(H, i,H.length-1);//从(H.length-1)/2位最后一个父亲节点,依次往前调整
}
for (int i=H.length-1; i >1 ; i--) {
_5快速排序.swap(H,1,i);//将堆顶(1)记录和当前未经排序子序列H[1.. i]中最后一个记录相互交换
HeapAdjust(H,1,i-1);//将H[1..i-1]重新调整为大顶堆
}
}
static void HeapAdjust(int[] H, int s, int m){ // H 堆,s 中间数,m 堆的长度
// 已知s.. m中记录的关键字除H[s]之外均满足堆的定义,本函数调整H[s]的关键字,使H[s..m]成为一个大顶堆
int rc=H[s];//先把父亲节点值存到rc中
for (int j = 2*s; j <=m ; j*=2) {//j为s的左孩子,循环一次后,j*2,变成最大孩子的左孩子
if(j<m&&H[j]<H[j+1]){//如果右孩子大于左孩子,j++,j就为最大孩子的坐标
j++;
}
if(rc>=H[j]){//父元素已经是两个孩子中最大的了,则for循环退出
break;
}
H[s]=H[j];//把最大孩子的值赋值给父亲节点
s=j;//s保存当前最大孩子的下标
}
H[s]=rc;//把父亲值赋值给最后一次孩子下标
}
//递归调整
static void HeapAdjust2(int A[],int i,int n){//i为父节点,本函数使A[i..n]成为大顶堆
int left=2*i;
int right=2*i+1;
if(left>n){//左孩子已经越界返回
return;
}
int max=left;
if(right<=n&&A[right]>A[left]){
max=right;
}//max指向左右孩子中较大的那个
if(A[i]>=A[max]){//如果A[i]把;两个孩子都大,不同调整
return;
}
_5快速排序.swap(A,max,i); //否则,找到两个孩子中较大的,和i交换
HeapAdjust2(A,max,n); //大孩子那个位置的值发生了变化,i变更为大孩子那个位置,递归调整
}
}
8、计数排序,空间换时间 可以说是最快的:O(N+k),k=maxOf(sourceArr), 用它来解决问题时必须注意如果序列中的值分布非常广(最大值很大,元素分布很稀疏),空间将会浪费很多 所以计数排序的适用范围是:序列的关键字比较集中,已知边界,且边界较小。
public class demo14 {
public static void main(String[] args) {
int[] arr = {9,8,55,66,81,99,88,38,66,18};
count(arr);
System.out.println(Arrays.toString(arr));
}
public static void count(int[] arr){
//第一步找出数组中最大值
int max = arr[0];
for (int i = 1; i < arr.length; i++) {
if (max < arr[i]){
max = arr[i];
}
}
//第二步创建辅助数组
int[] helpArr = new int[max+1];
//第三步进行计数
for (int i = 0; i < arr.length; i++) {
helpArr[arr[i]]+=1;
}
//第四步进行回填
int count = 0;
for (int i = 1; i < helpArr.length; i++) {
while (helpArr[i] > 0) { //当辅助数组的值大于0,就进行回填
arr[count++] = i; //
helpArr[i]--;
}
}
}
}
九、桶排序
先分桶,再用其他排序方法对桶内元素排序,按桶的编号依次检出。(分配-收集) 用它解决问题必须注意序列的值是否均匀地分布在桶中。如果不均匀,那么个别桶中的元素会远多于其他桶,桶内排序用比较排序,极端情况下,全部元素在一个桶内,还是会退化成NlgN 其时间复杂度是:时间复杂度: O(N+C),其中C=N*(logN-logM),约等于N*lgN,N是元素个数,M是桶的个数。
十、基数排序
kN级别(k是最大数的位数) 是整数数值型排序里面又快又稳的,无论元素分布如何,只开辟固定的辅助空间(10个桶) 对比桶排序,基数排序每次需要的桶的数量并不多。而且基数排序几乎不需要任何“比较”操作,而桶排序在桶相对较少的情况下, 桶内多个数据必须进行基于比较操作的排序。因此,在实际应用中,对十进制整数来说,基数排序更好用。
思路:
将整数按位数切割成不同的数字,然后按每个位数分别比较。 将所有待比较数值统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始,依次进行一次排序。 这样从最低位排序一直到最高位排序完成以后,数列就变成一个有序序列。
public class demo15 {
public static void main(String[] args) {
int arr[] = { 53, 3, 542, 748, 14, 214};
radixSort(arr);
System.out.println("基数排序后 " + Arrays.toString(arr));
}
//基数排序方法
public static void radixSort(int[] arr) {
//根据前面的推导过程,我们可以得到最终的基数排序代码
//1. 得到数组中最大的数的位数
int max = arr[0]; //假设第一数就是最大数
for(int i = 1; i < arr.length; i++) {
if (arr[i] > max) {
max = arr[i];
}
}
//得到最大数是几位数
int maxLength = (max + "").length();
//定义一个二维数组,存放表示10个桶,每一个桶的大小是 arr.length
int[][] bucket = new int[10][arr.length];
//为了记录每个桶中,实际存放了多少个数据,我们定义一个一维数组来记录各个桶的每次放入的数据个数
//可以这里理解
//比如:arrCount[0] , 记录的就是 bucket[0] 桶的放入数据个数
int[] arrCount = new int[10];
//这里我们使用循环将代码处理
for(int i = 0 , n = 1; i < maxLength; i++, n *= 10) {
//(针对每个元素的对应位进行排序处理), 第一次是个位,第二次是十位,第三次是百位..
for(int j = 0; j < arr.length; j++) {
//取出每个元素的对应位的值
int number = arr[j] / n % 10;
//放入到对应的桶中
bucket[number][arrCount[number]] = arr[j];
arrCount[number]++;
}
//按照这个桶的顺序(一维数组的下标依次取出数据,放入原来数组)
int index = 0;
//遍历每一桶,并将桶中是数据,放入到原数组
for(int k = 0; k < arrCount.length; k++) {
//如果桶中,有数据,我们才放入到原数组
if(arrCount[k] != 0) {
//循环该桶即第k个桶(即第k个一维数组), 放入
for(int l = 0; l < arrCount[k]; l++) {
//取出元素放入到arr
arr[index++] = bucket[k][l];
}
}
//第i+1轮处理后,需要将每个 bucketElementCounts[k] = 0 !!!!
arrCount[k] = 0;
}
}
}
}