数据结构算法系列3-排序算法

1. 冒泡排序

从数组头开始,比较相邻的元素。如果第一个比第二个大(小),就交换它们两个;对每一对相邻元素作同样的工作,从开始第一对到尾部的最后一对,这样在最后的元素应该会是最大(小)的数;重复步骤1~2,重复次数等于数组的长度,直到排序完成。

public static void sort(int[] array) {
        if (array.length == 0)
            return;
        for (int i = 0; i < array.length; i++) {
			for (int j = 0; j < array.length-i-1; j++) {
				if (array[j]>array[j+1]) {
					int tem = array[j];
					array[j] = array[j+1];
					array[j+1] = tem;
				}
			}
		}
    }

2. 简单选择排序

首先,找到数组中最大(小)的那个元素;
其次,将它和数组的第一个元素交换位置(如果第一个元素就是最大(小)元素那么它就和自己交换);
再次,在剩下的元素中找到最大(小)的元素,将它与数组的第二个元素交换位置。如此往复,直到将整个数组排序。
这种方法叫做选择排序,因为它在不断地选择剩余元素之中的最大(小)者。

 for (int i = 0; i < array.length; i++) {
        	int min = i;
			for (int j = i; j < array.length; j++) {
				if (array[min]>array[j]) {
					min=j;
				}
			}
			if (min!=i) {
				int tem = array[min];
				array[min] = array[i];
				array[i] = tem;
			}
    	}

3. 简单插入排序

对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。为了给要插入的元素腾出空间,我们需要将插入位置之后的已排序元素在都向后移动一位。插入排序所需的时间取决于输入中元素的初始顺序。

 public static void sort(int[] array) {
        if (array.length == 0)
            return;
        for (int i = 0; i < array.length-1; i++) {
        	int pre = i;
        	int curData = array[i+1];
        	while(pre>=0&&array[pre]>curData){
        		array[pre+1]=array[pre];
        		pre--;
        	}
        	array[pre+1]=curData;
    	}
      
    }

4. 希尔排序

希尔排序是把待排序数组按一定数量的分组,对每组使用直接插入排序算法排序;然后缩小数量继续分组排序,随着数量逐渐减少,每组包含的元素越来越多,当数量减至 1 时,整个数组恰被分成一组,排序便完成了。这个不断缩小的数量,就构成了一个增量序列。

 public static void sort(int[] array) {
        if (array.length == 0)
            return;
       
        int curData;
        int len = array.length;
        int gap=len / 2;
        while(gap>0){
        	 for (int i = 0; i <=array.length-1-gap; i++) {
             	int pre = i;
             	curData = array[i+gap];
             	while(pre>=0&&array[pre]>curData){
             		array[pre+gap]=array[pre];
             		pre = pre-gap;
             	}
             	array[pre+gap]=curData;
         	}
        	 gap= gap/2;
        }
    }

5. 归并排序

归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法,对于给定的一组数据,利用递归与分治技术将数据序列划分成为越来越小的半子表,在对半子表排序后,再用递归方法将排好序的半子表合并成为越来越大的有序序列。为了提升性能,有时我们在半子表的个数小于某个数(比如15)的情况下,对半子表的排序采用其他排序算法,比如插入排序。若将两个有序表合并成一个有序表,称为2-路归并,与之对应的还有多路归并。

 public static int[] sort(int[] array) {
        if (array.length < 2) return array;
        /*切分数组,然后递归调用*/
        int mid = array.length / 2;
        int[] left = Arrays.copyOfRange(array, 0, mid);
        int[] right = Arrays.copyOfRange(array, mid, array.length);
        return merge(sort(left), sort(right));
    }
    /**
     * 归并排序——将两段排序好的数组结合成一个排序数组
     *
     * @param left
     * @param right
     * @return
     */
    public static int[] merge(int[] left, int[] right) {
        int[] result = new int[left.length + right.length];
        for (int index = 0, i = 0, j = 0; index < result.length; index++) {
            if (i >= left.length)/*左边数组已经取完,完全取右边数组的值即可*/
                result[index] = right[j++];
            else if (j >= right.length)/*右边数组已经取完,完全取左边数组的值即可*/
                result[index] = left[i++];
            else if (left[i] > right[j])/*左边数组的元素值大于右边数组,取右边数组的值*/
                result[index] = right[j++];
            else/*右边数组的元素值大于左边数组,取左边数组的值*/
                result[index] = left[i++];
        }
     
        return result;
    }

6. 快速排序

快速排序是对冒泡排序的一种改进,也是采用分治法的一个典型的应用。
首先任意选取一个数据(比如数组的第一个数)作为关键数据,我们称为基准数。将所有比它小的数都放到它前面,所有比它大的数都放到它后面,这个过程称为一趟快速排序,也称为分区(partition)操作。
通过一趟快速排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数组变成有序序列。

public static void sort(int[] array, int start, int end) {
    	 if (array.length < 1 || start < 0 || end >= array.length || start > end)
             return ;
    	 int zoneIndex = realSort(array, start, end);
    	 if (zoneIndex>start) {
    		 sort(array, start, zoneIndex-1);
		}
    	 if (zoneIndex<end) {
    		 sort(array, zoneIndex+1, end);
		}
    }
   
    public static int realSort(int[] array, int start, int end){
    	//我们这里随机选择一个数当基准数,然后将基准数和数组最后一位互换
        int pivot = (int) (start + Math.random() * (end - start + 1));
        int zoneIndex = start - 1;
        swap(array, pivot, end);
        for (int i = start; i <=end; i++) {
			if (array[i]<=array[end]) {
				zoneIndex++;
				if (zoneIndex<i) {
					swap(array, zoneIndex, i);
				}
			}
		}
        return zoneIndex;
    } 
    public static void swap(int[] array, int i, int j) {
        int temp = array[i];
        array[i] = array[j];
        array[j] = temp;
    }

7. 堆排序

二叉堆,是一个完全二叉树的结构,同时满足堆的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。在一个二叉堆中,根节点总是最大(或者最小)节点。堆排序算法就是抓住了这一特点,每次都取堆顶的元素,然后将剩余的元素重新调整为最大(最小)堆,依次类推,最终得到排序的序列。
对于完全二叉树,对于位置为K的结点 左子结点=2k+1 右子结点=2(k+1)。最后一个非叶节点的位置为 (N/2)-1,N为数组长度。
排序的规则简单来说就是从最后一个非叶节点开始,从下到上,从右到左调整。

 public static void sort(int[] array) {
    	len = array.length;
        if (len < 1) return ;
        buildMaxHeap(array);
        PrintArray.print(PrintArray.SRC);
        while (len > 0) {
            swap(array, 0, --len);
            adjustHeap(array, 0);
        }
    }
    public static void buildMaxHeap(int[] array) {
    	int index = len/2-1;
    	for (int i = index; i>=0; i--) {
    		adjustHeap(array, i);
		}
    }
    public static void adjustHeap(int[] array, int i){
    	int left = 2*i+1;
    	int right = 2*i+2;
    	int max = i;
    	if (left < len &&array[left]>array[max]) {
    		max=left;
		}
    	if (right < len &&array[right]>array[max]) {
    		max=right;
		}
    	 if (max != i) {
             swap(array, max, i);
             adjustHeap(array, max);
         }
    	 
    }
    public static void swap(int[] array, int i, int j) {
        int temp = array[i];
        array[i] = array[j];
        array[j] = temp;
    }

8. 桶排序

桶排序 (Bucket sort)的工作的原理:假设输入数据服从均匀分布,利用某种函数的映射关系将数据分到有限数量的桶里,每个桶再分别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序)。实际上,桶排序的f(k)值的计算,其作用就相当于快排中划分,已经把大量数据分割成了基本有序的数据块(桶)。然后只需要对桶中的少量数据做排序即可。

 /**
     *
     * @param array
     * @param bucketSize BucketSize,作为每个桶所能放置多少个不同数值
     *                   (例如当BucketSize==5时,该桶可以存放{1,2,3,4,5}这几种数字,
     *                   但是容量不限,即可以存放100个3);
     * @return
     */
    public static ArrayList<Integer> sort(ArrayList<Integer> array, int bucketSize) {
        if (array == null || array.size() < 2)
            return array;
        int max = array.get(0), min = array.get(0);
        // 找到最大值最小值
        for (int i = 0; i < array.size(); i++) {
            if (array.get(i) > max)
                max = array.get(i);
            if (array.get(i) < min)
                min = array.get(i);
        }
        /*获得桶的数量*/
        int bucketCount = (max - min) / bucketSize + 1;
        /*构建桶*/
        ArrayList<ArrayList<Integer>> bucketArr = new ArrayList<>(bucketCount);
        ArrayList<Integer> resultArr = new ArrayList<>();
        for (int i = 0; i < bucketCount; i++) {
            bucketArr.add(new ArrayList<Integer>());
        }
        /*将原始数组中的数据分配到桶中*/
        for (int i = 0; i < array.size(); i++) {
            bucketArr.get((array.get(i) - min) / bucketSize).add(array.get(i));
        }
        /*看看桶中数据的分布*/
        for (int i = 0; i < bucketArr.size(); i++) {
            System.out.print("第"+i+"个桶包含数据:");
            PrintArray.printObject(bucketArr.get(i));
        }
        for (int i = 0; i < bucketCount; i++) {
            if (bucketSize == 1) {
                for (int j = 0; j < bucketArr.get(i).size(); j++)
                    resultArr.add(bucketArr.get(i).get(j));
            } else {
                if (bucketCount == 1)
                    bucketSize--;
                /*对桶中的数据再次用桶进行排序*/
                ArrayList<Integer> temp = sort(bucketArr.get(i), bucketSize);
                for (int j = 0; j < temp.size(); j++)
                    resultArr.add(temp.get(j));
            }
        }
        return resultArr;
    }

9. 总结

在这里插入图片描述
算法的稳定性
稳定:如果a原本在b前面,而a=b,排序之后a仍然在b的前面;
不稳定:如果a原本在b的前面,而a=b,排序之后a可能会出现在b的后面;
排序算法如果是稳定的,那么从一个键上排序,然后再从另一个键上排序,前一个键排序的结果可以为后一个键排序所用。
算法的复杂度
时间复杂度: 一个算法执行所耗费的时间。
空间复杂度:对一个算法在运行过程中临时占用存储空间大小的量度。
常见复杂度由小到大:O(1) < O(logn) < O(n) < O(nlogn) < O(n^2) < O(n^3) < O(2^n)
时间复杂度记忆
冒泡、选择、插入排序需要两个for循环,每次只关注一个元素,平均时间复杂度为
(一遍找元素O(n),一遍找位置O(n))
快速、归并、堆基于分治思想,log以2为底,平均时间复杂度往往和O(nlogn)(一遍找元素O(n),一遍找位置O(logn))相关
而希尔排序依赖于所取增量序列的性质,但是到目前为止还没有一个最好的增量序列 。例如希尔增量序列时间复杂度为O(n²),而Hibbard增量序列的希尔排序的时间复杂度为 , 有人在大量的实验后得出结论;当n在某个特定的范围后希尔排序的最小时间复杂度大约为n^1.3。
从平均时间来看,快速排序是效率最高
快速排序中平均时间复杂度O(nlog n),这个公式中隐含的常数因子很小,比归并排序的O(nlog n)中的要小很多,所以大多数情况下,快速排序总是优于合并排序的。
而堆排序的平均时间复杂度也是O(nlog n),但是堆排序存在着重建堆的过程,它把根节点移除后,把最后的叶子结点拿上来后需要重建堆,但是,拿上的值是要比它的两个叶子结点要差很多的,一般要比较很多次,才能回到合适的位置。堆排序就会有很多的时间耗在堆调整上。
虽然快速排序的最坏情况为排序规模(n)的平方关系,但是这种最坏情况取决于每次选择的基准, 对于这种情况,已经提出了很多优化的方法,比如三取样划分和Dual-Pivot快排。同时,当排序规模较小时,划分的平衡性容易被打破,而且频繁的方法调用超过了O(nlog n)为省出的时间,所以一般排序规模较小时,会改用插入排序或者其他排序算法。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值