内部排序 --- 插入排序

排序的分类标准有多种,如果按照排序过程中一句的不同原子对内部排序方法进行分类,大致可以分为:插入排序、交换排序、选择排序和基数排序。

// 直接插入排序 java
	/**
     * 直接插入排序 空间复杂度为1 时间复杂度n*n
     * @param arr
     */
    public void InsertSort(int[] arr){
        if(arr == null  || arr.length == 0){
            throw new RuntimeException("wrong parameters");
        }
        if(arr.length == 1)
            return;
        for(int i = 1;i<arr.length;i++){
            int tmp = arr[i];
            for(int j = i-1;j>=0;--j){
                if(tmp > arr[j]){
                    arr[1+j] = tmp;
                    break;
                }
                arr[1+j] = arr[j];
            }
        }
    }

直接插入排序优点是代码简单,适合n不大的情况。直接插入排序主要包括两个步骤:“比较”和“移动”,可以从这两个方面进行优化。

折半插入排序
相比于直接插入排序依次遍历有序部分查找最终插入位置,折半插入排序使用折半查找方式提高查找插入位置的速度是。

/**
     * 折半插入排序,使用折半查找方法在有序列表中找到待插入元素的位置
     * @param arr
     */
    public void BInsertSort(int[] arr){
        if(arr == null  || arr.length == 0){
            throw new RuntimeException("wrong parameters");
        }
        if(arr.length == 1)
            return;
        for(int i = 1;i<arr.length;++i){
            int tmp = arr[i];
            // 折半查找
            int low  = 0;
            int high = i-1;
            while(low <= high){ //  low和high当前位置元素并不能确定与tmp的大小关系,所以<=
                int mid = (low+high)/2;
                if(arr[mid] < tmp){
                    low = mid+1;
                }else {
                    high = mid-1;
                }
            }
            int j = i-1;
            while(j>high){
                arr[1+j] = arr[j];
            }
            arr[high+1] = tmp;
        }
    }

折半插入排序相比直接插入排序减少了查找元素的时间,但是移动元素方式和直接插入排序移动元素方式相同,所以时间复杂度任然是n*n。

2-路插入排序

/2-路插入排序
public void twoInsert1(int[] arr){
        if(arr == null  || arr.length == 0){
            throw new RuntimeException("wrong parameters");
        }
        if(arr.length == 1)
            return;

        int len = arr.length;
        int[] tmp = new int[arr.length];
        tmp[0] = arr[0];
        int end = 0;
        int start = 0;
        // 将arr中的元素插入到tmp数组中
        for(int i = 1;i<arr.length;++i){
            if(arr[i] >= tmp[end]){
                tmp[++end] = arr[i];
                continue;
            }else if(arr[i] <= tmp[start]){
                tmp[(start+len-1)%len] = arr[i];
                continue;
            }
            // 在start的end范围内使用二分查找,此时low和high是逻辑上的坐标
            int low = end;
            int high = start+len;
            int mid = 0;
            while(low < high){
                mid = (low + high)/2;
                if(tmp[mid%len] <= arr[i]){
                    low = mid + 1;
                }else{
                    high = mid - 1;
                }
            }
            // 移动元素,tmpIndex 是逻辑上的坐标
            int tmpIndex = start + len + 1;
            while(tmpIndex > high){
                tmp[tmpIndex%len] = tmp[--tmpIndex % len];
            }
            tmp[low % len] = arr[i];
        }
        // 将tmp中的元素放入arr中
        for(int k =0;k<len;k++){
            tmp[k] = tmp[(k+start)%len];
        }
    }

移动元素的次数大约是n*n/8,如果tmp[0]值是最大值或者最小值,将失去2路插入排序的优势。
参考:二路插入排序

表插入法(Java中有结构体吗?)
为了减少移动次数,可以将待排序数组中的元素以链表的形式组织起来,然后将未排序元素插入链表中。表插入法的思路是以表的结构表示链表,每次插入数据只需要更改相应坐标即可。

	/**
     * 表插入法,以表的形式表示链表,从而减小移动元素的次数,但是比较次数没法少,复杂度n*n。
     * @param arr 数组所有元素的next为空
     */
    public void tableInsert(TableNode[] arr){
        if(arr == null || arr.length <= 1){
            throw new RuntimeException("wrong exception");
        }
        if(arr.length == 2)
            return;

        // 构建有序的循环链表
        arr[0].value = Integer.MAX_VALUE;
        arr[0].next = 1;
        arr[1].next = 0;

        // 将未排序的元素插入链表中
        for(int i = 2;i<arr.length;i++){
            int pre = 0;
            int cur = arr[0].next;
            while(arr[i].value < arr[cur].value && cur!=0){
                pre = cur;
                cur = arr[cur].next;
            }
            arr[pre].next = i;
            arr[i].next = cur;
        }
    }

最终得到的是一个以表结构表示的链表,如果要实现对表中的元素进行随机查找,需要将对表进行重新排序。

	/**
     * 将表链表中的元素从小到大排序
     * @param arr
     */
    public void arrangeLinkTable(TableNode[] arr) {
        if (arr == null || arr.length <= 1) {
            throw new RuntimeException("wrong exception");
        }
        if (arr.length == 2)
            return;

        int cur = arr[0].next;
        int next = 0;
        for(int i = 0;i< arr.length-1;++i){// i为带插入元素在数组中的位置
            // 找出链表中下一个节点
            next = arr[cur].next;
            while(next <= i){
                next = arr[next].next;  
            }
            // 交换
            TableNode tmp = arr[cur];
            arr[cur] = arr[++i];
            arr[i] = tmp;
            arr[i].next = cur;
            cur = next;
        }
    }

希尔排序
如果待排序元素基本有序,直接插入排序具有很高的效率,基于这点,希尔排序将整个待排序数组分成若干个子序列进行直接插入排序,当待排序列基本有序时候再对全体进行一次直接插入排序。需要注意的是希尔排序子序列构成不是逐段分隔,而是将相隔某个增量的记录组成一个子序列。

/**
     * 对arr进行shell排序
     * @param arr
     * @param dk
     */
    public void shellInsert(int[] arr,int dk){
        for(int i = dk;i<arr.length;++i){
            if(arr[i] < arr[i-dk]){
                // 需要进行插入排序
                int tmp = arr[i];
                int k = i;
                while(arr[k] <= arr[k-dk] && k>=0){
                    arr[k] = arr[k-dk];
                    k = k-dk;
                }
                arr[k+dk] = tmp;
            }
        }
    }

    /**
	 * 希尔排序,offset数组中最后一个增量必须是1,并且是递减的
     * @param arr
     * @param offset
     */
    public void shellSort(int[] arr,int[] offset){
        for(int i = 0;i<offset.length;i++){
            shellInsert(arr,offset[i]);
        }
    }

希尔排序的复杂度与增量序列的函数,需要注意的是:应使增量序列中的值没有除1之外的公因子,并且最后一个增量值必须是1。

/**
     * 将数组的第一个元素放在数组中的最终位置,返回坐标
     * @param arr
     * @param low
     * @param high
     * @return
     */
    public int getPartition(int[] arr,int low ,int high){
        int slow = low -1;
        int fast = low;
        swap(arr,low,high);// 以high坐标的元素为中间值
        while(fast < high){
            if(arr[fast] <= arr[high]){
                ++ slow;
                if(slow != fast){
                    swap(arr,++slow,fast);
                }
            }
        }
        swap(arr,++slow,high);
        return slow;
    }
    
    public void quickSort1(int[] arr,int low ,int high){
        if(low >= high)
            return;
        int pivort = getPartition(arr, low, high);
        quickSort1(arr,low,pivort-1);
        quickSort1(arr,pivort+1,high);
    }

优化:在每次调用getPartition的时候设置isSwitched变量,如果从slow到high没有发生交换,则在低端子表不用进行排序。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值