推广 热搜: page  音视频  使用  个数  选择  搜索引擎  企业  父亲  百度  可以 

java快速排序的时间复杂度_程序猿必备排序算法及其时间复杂度分析

   日期:2025-01-03     作者:hubinusb    caijiyuan   评论:0    移动:http://ww.kub2b.com/mobile/news/19560.html
核心提示:常用的时间复杂度常数阶(O(1))说明: 只要代码中没有复杂的循环条件,无论代码的函数是多少,一律为常数阶(O(

常用的时间复杂度

常数阶(O(1))

说明: 只要代码中没有复杂的循环条件,无论代码的函数是多少,一律为常数阶(O(1))

int i=1;

int j=3;

int m=0;

m=i+j;

....

对数阶 (O(log_2n))

说明: 存在循环体,在不考虑循环体的代码执行情况,该循环体本该执行n次,但是循环体将改变循环变量i的大小,每执行一次,i就扩到两倍,即i=i*2,这样就导致循环体会加速趋近终止条件n;假设经过x次后,退出循环体,则有(2^x>=n),因此,(x=log_2n);同理,当(i=i*3时,x=log_3n),退出循环的速度更快,时间复杂度更小。

while(i

i=i*2;

}

线性阶(O(n))

说明: 存在循环体。循环体内代码执行的次数随着规模n的变化而变化,并且是线性变化

for(int i=0;i

int j=o;

j++;

}

线性对数阶(O(nlog_2n))

说明: 将时间复杂度为(O(log_2n))的代码重复执行n次,即为线性对数阶(O(nlog_2n))

//n为一个固定的常数

//i和j均为循环变量

while(i

while(j

j=j*2;

}

}

平方阶(O(n^2))

说明: 复杂度为(O(n))的代码执行了n次。

for(int i=0;i

for(int j=0;j

int m=0;

m++;

}

}

立方阶O(n^3)

说明 和平方阶原理一样,时间复杂度为(O(n^2))的代码执行n次

for(int i=0;i

for(int j=0;j

for(int k=0;k

int m=0;

m++;

}

}

}

综上分析不难发现:时间复杂度的大小关系为:(O(1)

程序员必备排序算法

程序猿必备的排序算法有

插入排序

简单直接插入

希尔排序

选择排序

简单选择排序

堆排序

交换排序

冒泡排序

快速排序

归并排序

基数排序(桶排序)

冒泡排序(Bubble Sort)

基本原理: 将待排序的数组从左到右(下标从小到大), 依次选取元素,并和其相邻的元素比较大小,如果逆序,则交换两个元素的位置,每进行一轮,数组较大的元素会移动到数组的尾部,如同水中泡泡逐渐上浮一样。重重复进行多轮操作,直到数组有序为止。

图解案例

以3 9 -1 10 20 为例进行讲解

冒泡排序的几个关键点

进行arr.length-1趟排序

每一趟排序均会将前面未被排序的最大元素“冒泡”到后半部分

每一趟进行两两元素的比较,n个元素需要比较n-1次即可

由于每一趟的任务都是将前面未经排序的部分中较大的元素“冒泡”到后面,也就是说经过i趟之后,数组的后面的i个元素都是已经排好序的。因此第i趟只需要进行arr.lenth-1-i次比较即可

Java代码实现部分

public class BubbleSort{

public static void bubbleSort(int arr[]){

int temp = 0;

boolean flag = false;// 表示是否进行过交换

for (int i = 0; i < arr.length - 1; i++) {//进行arr.length-1趟排序

for (int j = 0; j < arr.length - 1 - i; j++) {//每一趟排序都经过arr.length-1-i次两两比较

if (arr[j] > arr[j + 1]) {

// 交换

flag = true;

temp = arr[j];

arr[j] = arr[j + 1];

arr[j + 1] = temp;

}

}

//优化代码

if (flag == false) {

// 没有发生过交换,表明是有序的

break;

} else {

flag = false;// 重置flag,进行下次判断

}

}

}

}

时间复杂度分析

很显然,两个嵌套for循环,每一层for循环的时间复杂度均为线性复杂度(O(n)),故冒泡排序算法的复杂度为(O(n^2))

快速排序(QuickSort)

基本原理:快速排序属于交换排序,从数组中一个元素作为基准元素(首元素或者中间元素),根据基准元素将数组分成两个部分,前半部分数组的元素均小于该基准值,后半部分元素均大于该基准值;然后使用递归的思想,对这两个部分使用同样的方法进行快速排序。

图解案例

以[4,7,6,5,3,2,8,1]数组为例

快速排序的几个关键点

选基准元素(首、尾元素或者中间元素)

根据基准元素划分数组,使得基准的前面元素均大于基准,基准后面的元素均小于该值

Java代码实现

public void QuickSort(int[] array,int left,int right){

int l=left;

int r=right;

int pivot=array[(right+left)/2];//选取中间值为基准值

while(l

//从左边开始找,直到找到大于或等于基准元素的位置为止

while(array[l]

l++;

}

//当找到了大于或等于基准元素的位置时,跳到右边开始寻找

while(array[r]>pivot){

r++;

}

//此时,array[r]<=pivot<=array[l],因此交换顺序

int tmp=array[r];

array[r]=array[l];

array[l]=tmp;

//注意:当array[r]==pivot或者array[l]==pivot,移动l或者r,否者

//在这种情况下会进入死循环

if(array[l]==pivot){

r--;

}

if(array[r]==pivot){

l++;

}

}

//当l==r时,需要错开l和r,

if(l==r){

l++;

r--;

}

if(left

QuickSort(array,left,r);

}

if(right>r){

QuickSort(array,r,right);

}

}

时间复杂度: 显然选择排序的时间复杂度为(O(nlon^2n)),

选择排序(Selection Sort)

基本原理: 从array[0]array[n-1]中选择一个最小的元素与array[0]交换位置,此时array[0]为最小的元素;然后从array[1]array[n-1]中选取最小的元素与array[1]交换位置;选择的范围由全局所有变成0;所以需要经过n-1次选择和交换的过程。

图解案例

思路图解

初始数组:101,34,119,1

第一趟:1,34,119,101

第二趟:1,34,119,101

第三趟:1,34,101,119

选择排序的几个关键点

对于长度为n的元素需要经过n-1次选择的过程,因为最后一个元素不需要选择,它是唯一,当然最小。

在每以选择的时候需要找到该次选择的最小值,随着选择次数的递增,所选择的数组范围逐渐变小

java代码

public void SelectionSort(int[] array){

for(int i=0;i

int minIndex=i;//初始最小值对应的索引

int min=array[minIndex];//定义一个初始最小值

for(int j=i;j

if(array[j]

min=array[j];//更新最小值

minIndex=j;//更新最小值对应的索引

}

}

//交换最小值和array[i]的值

if(minIndex!=i){//否则没必要交换

//交换两个数组元素的标准步骤

array[minIndex]=array[i];

array[i]=min;

}

}

}

时间复杂度: 显然选择排序的时间复杂度为(O(n^2)),原因很简单:两个嵌套的for循环均为(O(n)),注意:(O(n-1)=O(n))

插入排序

基本原理: 将待排序数组array[0]~array[n-1]从逻辑上分为有序数组和无序数组,初始条件:(array[0])为有序数组,(array[1],..array[n-1])为无序数组;则插入排序的基本过程是:逐一将无序数组中的元素插入到有序数组中去,每完成一次插入操作,有序数组中的元素加一,无序数组中的元素减一,直到所有的无序数组变成有序数组。

图解案例

原始数组:3,1,2,5,4

第一次插入:3 1 2 5 4--->1 3 2 5 4

第二次插入:1 3 2 5 4--->1 2 3 5 4

第三次插入:1 2 3 5 4---->1 2 3 5 4 (实际上没有进行任何插入操作)

第4次操作 1 2 3 5 4 --->1 2 3 4 5

插入排序的几个关键点

从array[1]元素作为有序数组和无序数组的分割点

插入操作的代码实现,首先确定待插入的元素insertVal,对应的索引index,再确定需要插入的位置insertIndex=index-1,插入操作为:array[inserIndex]向后移动一位

public void insertSort(int[] array){

for(int i=1;i

int insertVal=array[i];//待插入的值为当前索引指向的值(在寻找待插入位置时会覆盖这个值,需要记录下来)

int insertIndex=i-1;//即将要插入的位置为当前索引的前一个位置

将当前元素与有序数组中的元素逐一比较,找到要插入的位置

while(insertIndex>=0 && array[insertIndex]>insertVal){

array[insertIndex+1]=array[insertIndex];//空出待插入的位置出来

insertIndex--;

}

//开始执行插入操作,同时有个优化点,当插入的位置为当前索引i时,不需要插入

//由于退出while时,insertIndex-1了,故真实的insertIndex=insertIndex+1;

if(insertIndex!=i){

array[insertIndex+1]=insertVal;

}

}

}

时间复杂度: 显然选择排序的时间复杂度为(O(n^2))

希尔排序

基本原理: 将待排序的数组按照某个预定的增量gap分组,对每个分组都采用直接插入排序的方式进行排序;随后逐步按照某个策略缩小gap(如:gap=gap/2)并进行分组,使用直接插入排序算法分别对这些数组进行排序,当gap=1时,只有一个分组,使用希尔排序完成最后的一次排序。

图解案例

希尔排序的关键步骤

分组(gap逐渐缩小)

对每个分组使用直接插入排序

Java代码实现部分

public void ShellSort(int[] array){

//初始化gap为数组长度的一半,每一次分组长度缩小一半

for(int gap=array.lentgh/2;gap>0;gap=gap/2){

//对当前分组中的所有组元素进行直接插入排序

for(int i=gap;i

int j=i;

int tmp=array[j];//暂时保存待插入的值

if(array[j]

while(j-gap>=0 && tmp

array[j]=array[j-gap];//空出待插入的位置

j-=gap;

}

//退出while循环时,证明找到了合适的插入位置

array[j+gap]=tmp;

}

}

}

}

时间复杂度

第一层for循环为分组功能,其时间复杂度为:(log_2n),内部希尔排序最差为(O(n^2))。

归并排序

基本原理:采用分治思想和递归思想。归并排序主要分为两大过程:分解和合并过程,分解是将数组二分至只有一个元素为止。合并的过程中同时进行排序,两边元素逐一比较,较小的元素填充到缓冲数组中。另外归并排序需要额外的缓存数组。

图解案例

分解过程使用了递归思想。

合并过程为

java代码

public void mergeSort(int[] array,int left,int right,int[] temp){

//递归分解

//那么递归的结束结束条件:left>=right

if(left>=right){

return;

}

int mid=(left+rightight)/2;

//向左边递归

mergeSort(array,int left,mid,temp);

//向右递归

mergeSort(array,int mid+1,right,temp);

merge(array,left,mid,right,temp);

}

public void merge(int[] array,int left,int mid,int right,int[] temp){

int i=left;//索引i范围为[left,mid];

int j=mid+1;//索引j的范围为[mid+1,right];

int t=0;//tmp额外数组的索引

while(i<=mid && j<=right){

if(array[i]

temp[t]=array[i];

t++;

i++;

}else{

temp[t]=array[j];

t++;

j++;

}

}

//当该循环退出的时候,有两种种情况(不可能同时超出边界,最多同时到达边界,但最后还是有一个先)

//超出边界

//1.i>mid,右边数组有剩余

//2.j>right,左边数组有剩余

if(i>mid){

while(j<=right){

temp[t]=array[j];

t++;

j++;

}

}

if(j>right){

while(i<=mid){

temp[t]=array[i];

t++;

i++;

}

}

//将temp数组中的元素复制到array中

//注意,temp中的有效部分并不是从0开始,而是从[left,right]

//因此只要复制的范围是[left,right];

t=0;//特别注意

int tmpLeft=left;

while(tmpLeft<=right){

array[tmpLeft]=temp[t];

t++;

tmpLeft++;

}

}

时间复杂度

第一层for循环为分组功能,其时间复杂度为:(log_2n),内部希尔排序最差为(O(nlog_2n))。

桶排序

图解

直接看图解,语言难于生动的描述此过程

基本步骤

(1)初始化1个二维数组10*array.length;表示10个桶,标号为:0-9,每个桶(用一个数组表示)最多容纳array.length个元素。

(2)获得待排序数组中最大元素的最高位,依次按个位、十位、...最高位进行排序。

(3)第一轮,按个位进行选取元素,将个位放进对应的桶编号中,当所有的元素均放进桶中后,按顺序读出每个桶中的元素,读取到的序列作为该轮排序的结果。再以该轮排序的结果作为输入条件,依照十位进行排序;最后直到所有的位数都完成。

编码过程中的几个关键点

获取元素的个位、十位、百位:array[i]/1%10 array[i]/10%10 array[i]/100%10

获取某个元素的最多有几位数:(n+"").length(),将整型强制转化为字符串类型,然后使用String的length()方法。

由于每完成一次“装桶”操作,需要从每个桶中读取序列,每个桶中个数不一,因此,可以定义一个数组用于记录每个桶中存放了多少个元素。

每一排序过程中:桶的编号和位值是一一对应的关系.

java代码

public class RadixSort {

public static void radixSort(int[] arr){

//第一轮:针对每个元素的个位进行排序

//定义一个2维数组,表示10个桶,每个桶都是一个一维数组

//为了防止数据的溢出,则每个一维数组,大小定为arr.length

//很明显,基数排序是使用空间换时间的经典排序算法

int[][] bucket=new int[10][arr.length];

//每个桶都需要一个游标指针。

//因此我们可以定义一个数组来记录每个桶的游标

int[] bucketElementCounts=new int[10];

//得到数组中最大数的位数

int max=arr[0];

for(int i=1;i

if(arr[i]>max){

max=arr[i];

}

}

//技巧:获取整数的最大位数

int maxLength=(max+"").length();

for(int i=0,n=1;i

for (int j = 0; j < arr.length; j++) {

// 取出每个元素的个位、十位、百位。。。

int digitOfElement = arr[j] / n % 10;

bucket[digitOfElement][bucketElementCounts[digitOfElement]] = arr[j];

bucketElementCounts[digitOfElement]++;

}

// 按照桶的顺序依次取出数据,并放入原来的数组

int index = 0;

// 遍历每一个桶

for (int k = 0; k < bucketElementCounts.length; k++) {

// 如果桶中有数据,我们才放入数据到原数组

if (bucketElementCounts[k] != 0) {

// 取出该个桶中的数据

for (int l = 0; l < bucketElementCounts[k]; l++) {

// 取出元素放入到原数组中

arr[index] = bucket[k][l];

index++;

}

}

// 第一轮处理后,需要将每个桶的清零

bucketElementCounts[k] = 0;

}

}

}

}

堆排序

堆排序的算法步骤

1.根据排序的顺序和逆序来构建大顶堆或者小顶堆

2.交换堆顶元素和最后一个叶子结点,即:arr[0]和arr[arr.length-1];

3.交换后,调整堆直至符合定义

代码参考如下

public class HeapSort {

public static void main(String[] args) {

Scanner sc=new Scanner(System.in);

String[] str=sc.nextLine().trim().split(" ");

int[] arr=new int[str.length];

for(int i=0;i

arr[i]=Integer.parseInt(str[i]);

}

heapSort(arr);

System.out.print(Arrays.toString(arr));

}

//堆排序的过程

//1.构建大顶堆

//2.堆顶的元素与最后一个元素交换,同时,最后一个元素将排除在堆之外,继续循环调整堆。

public static void heapSort(int[] arr){

makeHeap(arr);//构建大顶堆

for(int i=arr.length-1;i>=0;i--){

swap(arr,0,i);//交换堆顶元素和当前堆的最后一个元素

adjustHeap(arr,0,i);

}

}

//交换两个元素的顺序

public static void swap(int[] arr,int a,int b){

if(a>arr.length-1 || b>arr.length-1){

return ;

}

int tmp=arr[a];

arr[a]=arr[b];

arr[b]=tmp;

}

public static void adjustHeap(int[] arr,int i,int len){

int k=2*i+1;//以i为结点的左子结点

while(k

while(k+1

k++;//右结点大,指向右节点

}

//否则左结点要大,仍然指向左结点

if(arr[i]>arr[k]){

break;//此时,以i为根结点的树为大顶堆,直接跳出循环即可

}

swap(arr,i,k);//否则,交换两个位置的元素,使之成为大顶堆

//由于i和k元素发生的了交换,此时,还需要保证以k为根节点的树仍然是一颗二叉树,仍然使用这个循环

i=k;//以k为根

k=2*k+1;//当前根结点的左子节点

}

}

//构造一棵二叉堆

//构造过程:从最后一颗结点开始,逐个调整所有的子树,使得他们均符合大顶堆的特点

public static void makeHeap(int[] arr){

for(int i=arr.length/2-1;i>=0;i--){//要从最后一个父节点开始,arr.length/2-1;

adjustHeap(arr, i,arr.length );

}

}

本文地址:http://ww.kub2b.com/news/19560.html     企库往 http://ww.kub2b.com/ ,  查看更多

特别提示:本信息由相关用户自行提供,真实性未证实,仅供参考。请谨慎采用,风险自负。

 
 
更多>同类最新文章
0相关评论

文章列表
相关文章
最新动态
推荐图文
最新文章
点击排行
网站首页  |  关于我们  |  联系方式  |  使用协议  |  版权隐私  |  网站地图  |  排名推广  |  广告服务  |  积分换礼  |  网站留言  |  RSS订阅  |  违规举报  |  鄂ICP备2020018471号