各种排序算法的原理、实现及其时间复杂度

(1、冒泡排序  2、选择排序  3、插入排序  4、归并排序  5、快速排序  6、堆排序  7、希尔排序)


1、冒泡排序

原理:

  1.  比较相邻的元素。如果第一个比第二个大,就交换他们两个。
  2.  对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。
  3.  针对所有的元素重复以上的步骤,除了最后一个。
  4.  持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较
实现:
以数组{2,1,5,4,3}为例,实现过程如下图所示

代码:
public static void bubble(int [] a){
        for(int i=0;i<a.length-1;i++){
            for(int j=0;j<a.length-1-i;j++){
                if(a[j]>a[j+1]){
                    int temp=a[j];
                    a[j]=a[j+1];
                    a[j+1]=temp;
                }
            }
        }
    }
时间复杂度:若数组为正序,则一轮比较即可完成排序,时间复杂度为O(n)
若数组为逆序,则需要n-1轮起泡,每轮需要n-i-1次比较,时间复杂度为O(n^2)
2、选择排序
原理:在要排序的一组数中选出最小的数和第一个位置的数交换,在剩下的数中选择最下的和第二个位置的数交换,如此循环到最后一个数和倒数第二
个数比较。
实现:
仍以数组{2,1,5,4,3}为例,实现过程如下图所示

代码:
public static void select(int [] a){
        for (int i = 0; i < a.length; i++) {
            int min = a[i];
            int n=i;
            for(int j=i+1;j<a.length;j++){
                if(a[j]<min){
                    min = a[j];
                    n = j;
                }
            }
            a[n] = a[i];
            a[i] = min;
        }
    }
时间复杂度:O(n^2)
3、插入排序
原理:将第i个数与前i-1个数依次比较,如果有比i大的数则将i插入到这个数的前面。
实现:仍以数组{2,1,5,4,3}为例,实现过程如下图所示

代码:
public static void insert(int [] a){
        int temp=0 ;
        int j=0;
        for(int i=0;i<a.length;i++)
        {
            temp=a[i];
            //如果第i个数比前面的数小,就让前面的数向后移
            for(j=i;j>0&&temp<a[j-1];j --)
            {
                a[j]=a[j-1];
            }
            a[j]=temp;
        }
    }
时间复杂度:O(n^2)
以上是时间复杂度为O(n^2)的排序
下面介绍时间复杂度为O(nlogn)的排序
4、归并排序
原理:假设序列有n个元素,将此序列看成是n个长度为1的序列,将每两个相邻的序列进行归并操作,形成floor(n/2)个序列,排序后每个序列包含两个元素,将上述序列继续两两归并,形成floor(n/4)个序列排序,继续重复上一步,直至形成一个长度为n的序列。
实现:
以数组{3,5,4,2,1,8,9,7}为例,实现过程如下图所示

代码:
public static void sort(int[] data, int left, int right) {
        if (left < right){
            //找出中间索引
            int center = (left + right) / 2;
            //对左边数组进行递归
            sort(data, left, center); //递归时先调用的后返回,后调用的先返回,因此形参的值也跟随,根据栈的原则
            //对右边数组进行递归
            sort(data, center + 1, right); /*当center是0的时候,left=right,因此sort不再执行,继续*/

            //合并
            merge(data, left, center, right);
        }
    }

    public static void merge(int[] data, int left, int center, int right){
        int[] tmpArr = new int[data.length];
        int mid = center + 1;
        //third记录中间数组的索引
        int third = left;
        int tmp = left;
        while (left <= center && mid <= right){
            //从左右两个数组中取出小的放入中间数组,
            if (data[left]-data[mid]<= 0){
                tmpArr[third++] = data[left++];
            } else{
                tmpArr[third++] = data[mid++];
            }
        }
        //比较后剩余的其他数字部分依次放入中间数组
        while (mid <= right) {
            tmpArr[third++] = data[mid++];
        }
        while (left <= center) {
            tmpArr[third++] = data[left++];
        }
        //将中间数组中的内容复制拷回原数组
        //(原left~right范围的内容复制回原数组)
        while (tmp <= right){//已经排序好的数组

            data[tmp] = tmpArr[tmp++];
        }
        System.out.println("merge");
    }
5、快速排序
原理: 通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列
实现:以数组{3,5,4,2,1,8,9,7}为例,实现过程如下图所示

代码:
public static void  quickSort(int[] data, int start, int end) { 
		int i = start;    //相当于i,左  索引
		int j = end;    //相当于j,  右  索引
		if (i >= j)  { // 判断是否到中间了 ,到中间返回
			return; //返回
		}
		//确定指针方向的逻辑变量,也就是从左搜索还是向右搜索 
		boolean flag=true; //false:左->右  true:右->左
		while (i != j) { //如果i==j证明第一趟结束,每趟的标准值仅是一个,例如第一趟被比较值为49,
			if (data[i] > data[j]) { 
				//交换数字 :所有比它小的数据元素一律放到左边,所有比他大的数据元素一律放到右边,
				int temp = data[i]; 
				data[i] = data[j]; 
				data[j] = temp; 
				//只有经过数的交换后,才能把游标的移动位置改变,移动书序是交替改变
				//决定下标移动,还是上标移动 ,游标更改 走下一个数据
				flag = (flag == true) ? false : true; 
			//	flag=!flag;
			} 
			//将指针向前或者向后移动 ,第一次从左-》右,第二次从右-》左
			if(flag) {//true,右---》左
				j--; 
			}else{//false 左---》右
				i++;
			}				 
		} 
		i--; 
		j++; 
		
		quickSort(data, start, i); 
		quickSort(data, j, end); 
	}
快排时间复杂度最优为O(nlogn),最差为O(n^2)
空间复杂度O(logn)~O(n)
6、堆排序
原理:先将待排序的数列构造成一个大顶堆(每个节点的值都比他的子节点的值大),此时序列的最大值就是堆顶的根节点,将其序列最后的元素交换,然后将剩下的n-1个数继续构造成一个大顶堆,则根节点为倒数第二的数,将其与序列倒数第二个位置的元素交换,如此反复最终得到一个正序序列。
实现:以数组{9,79,46,30,58,49}为例,实现过程如下图所示

代码:
public static void heapSort(int [] data) {
        System.out.println("开始排序");
        int arrayLength = data.length;
        //循环建堆
		/*
		 *        9
		 *      /   \
		 *    79    46
		 *   / \   /
		 * 30  58 49
		 */
        //针对完全二叉树,大顶堆,最顶端的值(0号索引)肯定是在没swap之前的最大的值,
        //对其在数组里面进行排序交换,大的排在数组后面,虽然最大的在最上面,但是其他的数据并没有排好,因此,需要不停更换顶点,重新建堆,需要循环
        for (int i = 0; i < arrayLength - 1 ; i++ ){//5 4 3 2 1
            //建堆
            builMaxdHeap(data, arrayLength - 1 -i);//-i的原因,就是数组中没经过一次循环,最大的数已经在数组的后面了,就不用在建堆了
            //由于是大顶锥方式:锥顶是最大的数,交换堆顶和最后一个元素
            System.out.print("交换锥顶前");
            System.out.println(Arrays.toString(data));

            swap(data, 0 , arrayLength - 1 - i);
            System.out.print("交换锥顶后");
            System.out.println(Arrays.toString(data));
        }
    }

    private static void builMaxdHeap(int [] data, int lastIndex){//修建大顶堆
        //从lastIndex处节点(最后一个节点)的父节点开始
        //(lastIndex - 1)/2    为最后一个节点的父节点的索引(除根节点)

        //都是从子节点开始判断  k的值分别是 5 3 1 索引号依次判断
		/*
		 *        9
		 *      /   \
		 *    79    46
		 *   / \   /
		 * 30  58 49
		 */
        for (int i = (lastIndex - 1) / 2 ; i >= 0  ; i--){//索引号从大的开始
            //k保存当前正在判断的节点
            int k = i;
            //如果当前k节点的子节点存在,
            //k * 2 + 1 判断当前节点的子节点
            while (k * 2 + 1 <= lastIndex){//看当前要判断k的子节点是否存在。
                //k节点的左子节点的索引
                int biggerIndex = 2 * k  + 1;
                //如果biggerIndex小于lastIndex,即biggerIndex + 1
                //代表k节点的右子节点存在
                if (biggerIndex < lastIndex){//防止溢出
                    //如果右子节点的值较大,biggerIndex存储的都是最大节点索引
                    if(data[biggerIndex]-(data[biggerIndex + 1]) < 0){//左面的小于右面的
                        //biggerIndex总是记录较大子节点的索引,之后再和父亲比
                        biggerIndex++; //这是较大节点的索引号,也就是右节点的索引号
                    }
                }
                //如果k(父节点)节点的值小于其较大子节点(左右都有可能)的值,交换
                if(data[k]-(data[biggerIndex]) < 0){
                    //交换它们
                    swap( data,k , biggerIndex);
                    //将biggerIndex赋给k,开始while循环的下一次循环,
                    //重新保证k节点的值大于其左、右子节点的值。
                    k = biggerIndex;//大节点的索引号,
                }else{//满足的条件刚好是节点的值大于子节点的值
                    break;//刚好进入下一层循环
                }
            }
        }
    }
    //交换data数组中i、j两个索引处的元素
    private static void swap(int [] data, int i , int j){
        int tmp = data[i];
        data[i] = data[j];
        data[j] = tmp;
    }
7、希尔排序
原理: 把记录按 步长  gap  分组,对每组记录采用 直接插入排序 方法进行排序。 随着 步长逐渐减小 ,所分成的组包含的记录越来越多,当步长的值减小到  1  时,整个数据合成为一组,构成一组有序记录,则完成排序。
实现:

代码:
public void shellSort(int[] list) {
        int gap = list.length / 2;
        while (1 <= gap) {
            // 把距离为 gap 的元素编为一个组,扫描所有组
            for (int i = gap; i < list.length; i++) {
                int j = 0;
                int temp = list[i];
                // 对距离为 gap 的元素组进行排序
                for (j = i - gap; j >= 0 && temp < list[j]; j = j - gap) {
                    list[j + gap] = list[j];
                }
                list[j + gap] = temp;
            }
            gap = gap / 2; // 减小增量
        }
    }

(希尔排序的文字描述、图片、代码均来自->  希尔排序














评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值