数据结构

概述
    数据结构是指数据在计算机内存空间或磁盘中的组织形式
    算法是完成特定任务的过程,如查找,添加,删除,排序等
    


数据结构
    名称        优点                                    缺点
    数组        插入快,如果知道下标,可以非常快的存取    查找慢,删除慢,大小固定
    有序数组    比无序数组查找快                        删除和插入慢,大小固定
    栈          提供后进先出方式的存取                  存取其他项很慢
    队列        提供先进先出方式的存取                  存取其他项很慢
    链表        插入快,删除快                           查找慢
    二叉树      查找,插入,删除都快(如果树保持平衡)      删除算法复杂
    红黑树      查找,删除,删除都快.树总是平衡的         算法复杂
    234树       查找,删除,删除都快.树总是平衡的         算法复杂
    哈希表      如果关键字已知存取极快.插入快           删除慢,如果不知道关键字则存取很慢,对存储空间使用不充分
    堆          插入,删除快,对最大数据项的存取很快      对其他数据项存取慢
    图          对现实世界建模                          有些算法慢且复杂
    以上数据结构除了数组之外,都可以被认为是抽象数据结构
    


算法
    算法是完成特定任务的过程,对某些结构中的数据进行处理
    分类有: 查找,插入,删除,遍历,排序,递归等.



大O表示法
    用来表示算法的时间复杂度和空间复杂度
    描述算法的效率和数据量之间的关系
    不要常数
    常见值
        O(1)
        O(N)
        O(logN)
        O(N^2)



数组
    特点
        内存中一块连续的空间
        长度在分配内存时已经确定,不可改变
        可以使用下标访问每一个元素,查询速度快
    数组常用算法
        算法                运行时间
        线性查找            O(N)
        二分查找            O(logN)
        无序数组的插入      O(1)
        有序数组的插入      O(N)
        无序数组的删除      O(N)
        有序数组的删除      O(N)
    数组排序算法
        假设有N个元素
        冒泡排序
            步骤
                相邻两个元素比较,大的放到右边
                从左向右比较,每比较一个向右移一个继续比较(比较一轮后会将最大的放到最右边)
                重复上边的过程直到排序完成
            特点
                最简单,但效率低
                每两个元素之间都进行一次比较
                每次比较有一般概率进行交换位置
            效率
                总共比较n-1轮
                第i轮比较的个数是n-i
                第i轮平均交换的个数是(n-i)/2
                总共比较的次数是        N*(N-1)/2   --> O(N^2)
                平均总共交换的次数是    N*(N-1)/4   --> O(N^2)
            实现
                int[] arr = {1,5,3,7,2};
                System.out.println(Arrays.toString(arr));
                for(int i=1; i<arr.length; i++){ //比较n-1轮
                    for(int j=0; j<arr.length-i; j++){ //第i轮比较n-i次
                        if(arr[j]>arr[j+1]){ //前一个和后一个比较,如果前面的大,则互换位置
                            int temp = arr[j];
                            arr[j] = arr[j+1];
                            arr[j+1] = temp;
                        }
                    }
                }
                System.out.println(Arrays.toString(arr));
        选择排序
            步骤
                将所有元素对比一遍,找到最大的,放到最右边
                重复上边的过程直到排序完成
            特点
                每一轮只交换一次位置
            效率
                总共比较n-1轮
                第i轮比较的个数是n-i
                第i轮大约交换的个数是1
                总共比较的次数是        N*(N-1)/2   --> O(N^2)
                平均总共交换的次数是    N-1         --> O(N)
            实现
                int[] arr = {1,5,3,7,2};
                System.out.println(Arrays.toString(arr));
                for(int i=1; i<arr.length; i++){ //比较n-1轮
                    int maxIndex = 0;
                    for(int j=1; j<=arr.length-i; j++){ //第i轮比较n-i次
                        if(arr[j]>arr[maxIndex]){
                            maxIndex = j; //记录最大值的位置
                        }
                    }
                    //交换位置
                    if(maxIndex!=arr.length-i){
                        int temp = arr[arr.length-i];
                        arr[arr.length-i] = arr[maxIndex];
                        arr[maxIndex] = temp;
                    }
                }
                System.out.println(Arrays.toString(arr));
        插入排序
            步骤
                从左开始,第i轮将左边i+1个元素顺序排好
                轮数从1到i-1,直到排序完成
            特点
                对于已经有序或基本有序的数据,插入排序要快得多
            效率
                总共比较n-1轮
                第i轮平均比较的个数是i/2
                第i轮平均交换的个数是i/2
                平均总共比较的次数是    N*(N-1)/4   --> O(N^2)
                平均总共交换的次数是    N*(N-1)/4   --> O(N^2)
            实现
                int[] arr = {1,5,3,7,2};
                System.out.println(Arrays.toString(arr));
                for(int i=1; i<arr.length; i++){ //比较n-1轮
                    int temp = arr[i]; //temp是新插入的元素
                    for(int j=i; j>0; j--){ //第i轮比较i次
                        if(temp < arr[j-1]){ //将新插入的元素倒着和之前的元素比较
                            arr[j] = arr[j-1];
                        }else{
                            arr[j] = temp; 
                            break;
                        }
                    }
                }
                System.out.println(Arrays.toString(arr));



栈
    特点
        只允许访问一个元素:栈顶的数据
        向栈顶添加数据叫压栈
        从栈顶删除数据叫出栈,相邻的元素成为新的栈顶
        先进后出
    使用数组实现的栈
        逻辑
            概述
                数据使用数组存储
                数组中最大索引的元素就是栈顶的元素
            进栈push
                将元素添加到数组末尾,并记住最大索引
            出栈push
                将最大索引的元素返回,并将最大索引减一(新的栈顶)
            查看栈顶元素
                将最大索引的元素返回
        效率
            进栈            O(1)
            出栈            O(1)
            查看栈顶        O(1)
        实现
            class MyStack<T>{
                private Object[] arr = null;//存储数据的数组
                private Integer size = 0;//栈中元素的个数
                
                public MyStack(int size){
                    arr = new Object[size];
                }
                
                //进栈
                public void push(T t){
                    if(isFull()){
                        throw new RuntimeException("栈已满,size:"+arr.length);
                    }
                    arr[size] = t;
                    size++;
                }
                
                //出栈
                public T pop(){
                    T t = peek();
                    size --;
                    return t;
                }
                
                //返回栈顶元素
                public T peek(){
                    if(!hasNext()){
                        throw new RuntimeException("栈为空");
                    }
                    return (T) arr[size-1];
                }
                
                //返回栈顶是否存元素
                public boolean hasNext(){
                    return size > 0;
                }
                
                //返回栈是否已满
                public boolean isFull(){
                    return size == arr.length;
                }
            }
            测试
                MyStack<String> stack = new MyStack<String>(5);
                stack.push("1");
                stack.push("2");
                stack.push("3");
                System.out.println(stack.pop());
                System.out.println(stack.pop());
                System.out.println(stack.pop());
                stack.push("1");
                stack.push("2");
                stack.push("3");
                stack.push("4");
                stack.push("5");
                System.out.println(stack.pop());
                System.out.println(stack.pop());
                System.out.println(stack.pop());



队列
    特点
        只允许在头部删除元素,只允许在尾部添加元素
        先进先出
    使用数组实现的队列
        逻辑
            概述
                数据使用数组存储
                存在两个变量,分别记录队头和队尾的索引
                队头索引默认为0,队尾索引默认为-1(表示是空队列)
            从尾部插入元素
                将队尾索引+1
                将元素添加到数组末尾
            从头部取出元素
                返回元素队头所在索引的元素,并将队头索引+1
            查看头部元素
                返回元素队头所在索引的元素
            获取队列长度
                队尾索引 - 队头索引
        循环队列(缓冲环)
            假设数组长度为10,存入9个元素,取出5个元素,则队头索引为5,队尾索引为8
            这时队列最多只能再添加一个元素, 而索引为0-4的空间处于闲置状态
            为了合理利用空间,将后续添加的元素会放到队头前面的闲置空间中,这就是循环队列,也称缓冲环
            需要使用额外的变量记录队列的长度,因为队列为空或队列已满处于同样的状态:队尾索引比队头索引少1
            从尾部插入元素
                将队尾索引+1
                如果队尾索引==数组长度,则令队尾索引=0
                将元素添加到数组队尾索引处
                将队列长度+1
            从头部取出元素
                返回元素队头所在索引的元素,并将队头索引+1
                将队列长度-1
                如果队头索引==数组长度,则令队头索引=0
            查看头部元素
                返回元素队头所在索引的元素
            获取队列长度
                返回队列长度的变量值
        效率
            从尾部插入元素      O(1)
            从头部取出元素      O(1)
            查看头部元素        O(1)
        实现
            class MyQueue<T>{
                private Object[] arr = null;
                private Integer head = 0;//头部索引
                private Integer foot = -1;//尾部索引
                private Integer size = 0;//队列长度
                
                public MyQueue(int size){
                    arr = new Object[size];
                }
                
                //从尾部添加元素
                public void add(T t){
                    if(isFull()){
                        throw new RuntimeException("队列已满,size:"+arr.length);
                    }
                    foot ++;
                    if(foot==arr.length){
                        foot = 0;
                    }
                    arr[foot] = t;
                    size ++;
                }
                
                //从头部取出元素
                public T get(){
                    T t = peek();
                    head ++;
                    if(head==arr.length){
                        head = 0;
                    }
                    size --;
                    return t;
                }
                
                //从头部查看元素
                public T peek(){
                    if(!hasNext()){
                        throw new RuntimeException("队列为空");
                    }
                    return (T) arr[head];
                }
                
                //返回队列长度
                public Integer size(){
                    return size;
                }
                
                //返回队列是否为空
                public boolean hasNext(){
                    return size!=0;
                }
                
                //返回队列是否已满
                public boolean isFull(){
                    return size == arr.length;
                }
                
                //返回队列的内容
                public String toString(){
                    if(!hasNext()){
                        return "";
                    }
                    
                    StringBuilder builder = new StringBuilder();
                    if(head > foot){
                        foot = foot + arr.length;
                    }
                    for(int i=head; i<=foot; i++){
                        int index = i;
                        if(i>=arr.length){
                            index = i - arr.length;
                        }
                        builder.append(arr[index]).append(", ");
                    }
                    builder.deleteCharAt(builder.length()-1);
                    builder.deleteCharAt(builder.length()-1);
                    return builder.toString();
                }
            }
            测试
                MyQueue<String> queue = new MyQueue<String>(5);
                queue.add("1");
                queue.add("2");
                queue.add("3");
                queue.add("4");
                queue.add("5");
                System.out.println(queue.get());
                System.out.println(queue.get());
                System.out.println(queue.get());
                System.out.println(queue.get());
                queue.add("6");
                queue.add("7");
                queue.add("8");
                System.out.println(queue);
    优先级队列
        概述
            即队列中的元素是根据优先级排序的
            插入元素的位置可能在队列任意位置(需要对比优先级)
            删除元素只会从末尾删除
        效率
            当插入一个元素时,平均需要进行N/2次对比,N/2次移动, 所以效率为O(N)
            删除队尾元素的效率为 O(1)
            查看队头元素的效率为 O(1)



抽象数据类型(ADT)
    是一种考虑数据接口的方式,着重于它做了什么,而忽略它是怎么做的



链表
    特点
        由一系列节点组成, 每个节点包含对下一个节点的引用
        可动态添加,删除节点,大小不固定
        通过遍历来查询节点,速度较慢
    单向链表
        逻辑
            概述
                存在节点对象,存在下一个节点的引用next 和 一个数据对象data
                存在一个管理对象, 包含着对第一个节点的引用first
            向头部添加元素
                创建节点对象,存储指定的元素
                将first指向新创建的节点对象
                并将first的next指向原来的first
            删除元素
                从first开始遍历节点
                如果找到,并且是first,将first指向first.next
                如果找到,并且不是first,将上一个节点的next指向当前节点的next
            查询元素
                从first开始遍历节点
        效率
            添加元素        O(1)
            删除元素        O(N)
            查询元素        O(N)
        实现
            //节点类
            class MyLink<T>{
                public MyLink<T> next; //存储下一个节点的引用
                public T data; //存储的数据
                
                public MyLink(T data){
                    this.data = data;
                }
            }

            //节点管理类
            class MyLinkList<T>{
                private MyLink<T> first; //存储第一个节点的引用
                
                //返回链表是否是空的
                public boolean isEmpty(){
                    return first == null;
                }
                
                //在链表头部添加一个元素
                public void addFirst(T data){
                    MyLink<T> lastFirst = first;
                    first = new MyLink<T>(data);
                    first.next = lastFirst;
                }
                
                //删除元素
                public T delete(T data){
                    if(isEmpty()){
                        throw new RuntimeException("链表为空");
                    }
                    
                    //如果是first
                    if(first.data.equals(data)){
                        first = first.next;
                        return first.data;
                    }
                    
                    //查询中间的节点
                    MyLink<T> current = first;
                    MyLink<T> last = first;
                    
                    while( (current=current.next)!=null ){
                        if(current.data.equals(data)){
                            last.next = current.next;
                            return current.data;
                        }
                        last = current;
                    }
                    //没有找到
                    return null;
                }
                
                //查询元素
                public T find(T data){
                    if(isEmpty()){
                        return null;
                    }
                    
                    //如果是first
                    if(first.data.equals(data)){
                        return first.data;
                    }
                    
                    //查询中间的节点
                    MyLink<T> current = first;
                    while( (current=current.next)!=null ){
                        if(current.data.equals(data)){
                            return current.data;
                        }
                    }
                    //没有找到
                    return null;
                }
                
                //返回内部元素的值
                public String toString(){
                    StringBuilder builder = new StringBuilder();
                    if(!isEmpty()){
                        builder.append(first.data + ", ");
                        
                        MyLink<T> current = first;
                        while( (current=current.next)!=null ){
                            builder.append(current.data + ", ");
                        }
                        builder.deleteCharAt(builder.length()-1);
                        builder.deleteCharAt(builder.length()-1);
                    }
                    return "["+builder+"]";
                }
            }
            测试
                MyLinkList<String> list = new MyLinkList<String>();
                list.addFirst("1");
                list.addFirst("2");
                list.addFirst("3");
                
                System.out.println(list);
                list.delete("2");
                System.out.println(list);
                System.out.println(list.find("3"));
    双端链表
        概述
            即节点管理类中,添加一个对最后节点的引用
            可快速在末尾添加节点
    双向链表
        概述
            在每个节点中,包含对前一个节点的引用
            可向前查找节点
    迭代器
        概述
            用来遍历链表的类
            每个迭代器存在一个指向链表当前节点的引用
            每次获取节点会将current指向下一个节点
            迭代器可对链表实现遍历,添加,删除等操作
    练习
        用链表实现栈和队列



递归
    概述
        递归是一种方法自己调用自己的编程技术
        递归就是程序设计中的数学归纳法
        递归的功能总是可以被一个使用栈的方法实现(在简单情况下也可以被一个循环实现)
    特点
        方法调用自身
        每次调用自身都是为了解决更小的问题
        存在一个中止条件(问题简单到可以直接解决)
    效率
        递归会造成效率降低
            方法调用要慢于循环
            需要存储中间参数和返回值
        人们常常采用递归,是因为它从概念上简化了问题,而不是因为效率
    理解
        找规律就是解题的过程
        每个结果由前一个结果推导得出
        运行的规则是从 结束条件开始(例n=1),不断推导,得出给定值(n=5)的结果
    例子
        求阶乘
            规则
                n的阶乘 = n * (n-1) * (n-2) * ... 1
                5的阶乘 = 5 * 4 * 3 * 2 * 1
            思路(n!代表n的阶乘)
                n! = (n-1)! * n
                1! = 1
            实现
                public static int factorial(int i){
                    if(i==1){
                        return 1;
                    }else{
                        return factorial(i-1) * i;
                    }
                }
        汉诺塔问题
            规则
                存在三个塔座,上面放有若干盘子
                每个盘子的直径不同,且小的只能放到大的上边
                每次只能移动一个盘子,且盘子只能在三个塔座上移动
                将所有盘子从某个塔座移动到另一塔座最少需要多少步
            思路
                假设有n个盘子在A塔座上,要移动到C塔座上,B塔座为中介
                将所有n个盘子移动到C塔座的步骤
                    n>1
                        将第n-1个盘子及以上所有盘子从A塔移动到B塔座
                        将第n个盘子从A塔移动到C塔座
                        将第n-1个盘子及以上所有盘子从B塔移动到C塔座
                    n=1
                        将第n个盘子从A塔移动到C塔座
            实现
                /**
                 * @描述:汉诺塔问题
                 * @开发人员:likaihao
                 * @开发时间:2016-6-29 下午8:33:26
                 * @param n 移动n个盘子
                 * @param from 源塔
                 * @param to 目标塔
                 * @param center 中介塔
                 */
                public static void move(int n, String from, String to, String center){
                    if(n==1){
                        System.out.println("将编号为"+n+"的盘子从"+from+"移动到"+to);//将第n个盘子从A塔移动到C塔座
                        return;
                    }else{
                        move(n-1,from,center,to);//将第n-1个盘子及以上所有盘子从A塔移动到B塔座
                        System.out.println("将编号为"+n+"的盘子从"+from+"移动到"+to);//将第n个盘子从A塔移动到C塔座
                        move(n-1,center,to,from);//将第n-1个盘子及以上所有盘子从B塔移动到C塔座
                    }
                }
            测试
                move(3,"A","B","C");
        组合问题
            规则
                列出给定n个字符的所有组合
            思路
                全排列n个字符
                    n>1
                        全排列右边的n-1个字符
                        轮换所有n个字符 (所有字符左移一位,最左边的变到最右边)
                        将前两步重复n次 (保证每个字符都可以为开头)
                    n=1
                        无需排列
            实现
                /**
                 * @描述:打印指定字符串数组的所有排列方式
                 * @开发人员:likaihao
                 * @开发时间:2016-7-2 下午2:36:30
                 * @param arr 要全排列的数组
                 * @param n 要排列的字符数(从末尾开始计算)
                 */
                public static void permutation(char[] arr, Integer n){
                    if(n==1){
                        //一种排列结果,打印
                        System.out.println(Arrays.toString(arr));
                        return;
                    }else{
                        for(int j=0;j<n;j++){ //重复n次 (保证每个字符都可以为开头)
                            //全排列右边的n-1个字符
                            permutation(arr, n-1);
                            
                            //轮换所有n个字符 (所有字符左移一位,最左边的变到最右边)
                            char temp = arr[arr.length-n];
                            for(int i=arr.length-n;i<arr.length-1;i++){
                                arr[i] = arr[i+1];
                            }
                            arr[arr.length-1] = temp;
                        }
                    }
                }
            测试
                char[] arr = {'a','b','c','d'};
                permutation(arr,arr.length);
    归并排序
        特点
            将数组分割为多个小区域,各区域两两进行排序合并,最后组成排序后的数组
        思路
            按顺序合并两个区域(归并)
                两个区域原本都是有序的,两个区域是相邻的
                每次比较两个数组的前两个元素,取出最小的,放入新数组
                不断重复比较,直到完成合并
                将结果(新数组)复制到原来的数组
            排序
                当区域长度等于1时,无需排序
                当区域长度大于1时
                    将区域分成两个相邻的更小的区域
                    将第一部分排序
                    将第二部分排序
                    按顺序合并两个区域
        效率
            O(N * logN)
            复制次数 N * logN * 2
            比较次数 总是比复制次数小
        实现
            //对数组归并排序的便捷方法
            public static void sort(Integer[] arr){
                sort(arr,0,arr.length-1);
            }
        
            /**
             * @描述:对数组进行归并排序
             * @开发人员:likaihao
             * @开发时间:2016-6-30 下午3:10:51
             * @param arr 要排序的数组
             * @param start 要排序的区域的开始索引
             * @param end 要排序的区域的结束索引
             */
            public static void sort(Integer[] arr, int start, int end){
                if(start == end){
                    //当区域长度等于1时,无需排序
                    return;
                }else{
                    //当区域长度大于1时
                    //将区域分成两个相邻的更小的区域
                    int center = (start + end)/2;
                    sort(arr,start,center);//将第一部分排序
                    sort(arr,center+1,end);//将第二部分排序
                    merge(arr,start,center,end);//按顺序合并两个区域
                }
            }

            /**
             * @描述:将数组的两个相邻区域按顺序合并(两个区域都是各自有序的)
             * @开发人员:likaihao
             * @开发时间:2016-6-30 下午2:29:08
             * @param arr 待处理的数组
             * @param oneStart 第一个区域开始索引
             * @param oneEnd 第一个区域结束索引(同时是第二个区域开始索引-1)
             * @param twoEnd 第二个区域结束索引
             */
            public static void merge(Integer[] arr, int oneStart, int oneEnd, int twoEnd){
                int lastOneStart = oneStart;//记录一下开始位置
                int twoStart = oneEnd + 1;
                System.out.println("合并,范围:"+oneStart+"~"+oneEnd+","+twoStart+"~"+twoEnd);
                
                Integer[] arr2 = new Integer[twoEnd - oneStart + 1];//将结果存入新数组中
                int arr2Index = 0;
                while(true){
                    //将两个区域第一个元素进行比较,最小的放入新数组
                    if(arr[oneStart] <= arr[twoStart]){
                        arr2[arr2Index] = arr[oneStart];
                        oneStart ++;
                    }else{
                        arr2[arr2Index] = arr[twoStart];
                        twoStart ++;
                    }
                    
                    arr2Index++;
                    
                    //如果某个数组的元素比较完了,则退出循环
                    if(oneStart > oneEnd || twoStart > twoEnd){
                        break;
                    }
                }
                
                //如果某个数组中还有剩余的元素,则直接放到新数组末尾
                if(oneStart <= oneEnd){
                    for(int i=oneStart;i<=oneEnd;i++){
                        arr2[arr2Index] = arr[i];
                        arr2Index ++;
                    }
                }
                if(twoStart <= twoEnd){
                    for(int i=twoStart;i<=twoEnd;i++){
                        arr2[arr2Index] = arr[i];
                        arr2Index ++;
                    }
                }
                
                //将结果拷贝到原数组中
                for(int i=0;i<arr2.length;i++){
                    arr[lastOneStart+i] = arr2[i];
                }
                System.out.println(Arrays.toString(arr));
            }
        测试
            Integer[] arr = {3,2,8,5,10,1,23,56,11,89,27,48,36,11,2};
            sort(arr);



数组高级排序
    希尔排序
        特点
            使用不同的增量进行多次插入排序
            减少了插入排序移动的次数
            增量的规则通常是 3x+1 ... 1 的递减
        思路
            找到最大的增量(增量的规则是 3x+1)
            按增量进行循环,使用插入排序对选中的元素进行排序
            减少增量,重复上边的步骤,直到增量为1,排序结束
        效率
            O(N^2) 和 O(N * logN) 之间
        实现
            int[] arr = {1,12,17,14,6,9,2,86,45,44,33,8,4,23};
            //计算初始增量
            int n = 1;
            while(3 * n+1 < arr.length){
                n = 3 * n+1;
            }
            //排序
            for(;n>=1;n=(n-1)/3){ //增量递减,直到为1
                for(int i=n;i<arr.length;i++){//循环为从n到数组末尾(保证每个元素都会被比较)
                    //比较i和i-n个元素,小的在前边
                    int temp = arr[i]; //要插入的元素
                    int tempIndex = i; //要插入的元素的索引
                    //如果要插入的元素比前边的元素小,则将前边的元素统一向后移一位,将要插入的元素放到适当位置
                    while(tempIndex-n>0 && temp < arr[tempIndex-n]){ 
                        arr[tempIndex] = arr[tempIndex-n];
                        tempIndex = tempIndex-n;
                    }
                    arr[tempIndex] = temp;
                }
            }
            System.out.println(Arrays.toString(arr));
    快速排序
        特点
            划分:选定一个枢纽,将数据分为两组,小的在左,大的在右
            递归:将划分后的左边和右边分别进行快速排序(重复划分并排序)
        思路
            划分
                存在两个指针,left初始在数组最左边,right初始在数组最右边
                选定一个标志值
                left向右移动,碰到比标识值大的就停止
                right向左移动,碰到比标识值小的就停止
                如果left<right,则交换两个元素,两个指针继续移动
                如果left>right,则划分结束
            快速排序
                如果数组长度小于等于1
                    排序完成
                如果数组长度大于1
                    进行快速排序
                        将数组划分
                        将左边进行快速排序
                        将右边进行快速排序
        枢纽值的选择
            规则
                应选择具体的一个数据项作为枢纽
                可选择任意的数据项作为枢纽
                划分完成后枢纽在排序后的正确位置会被确定(总在两组交界处),即枢纽无需排序
            选择最右边的值作为标志值
                划分时可跳过最右边的值
                划分结束后将 最右边的值(枢纽) 和 右边的组的最左边元素 交换位置
                这样左右分别进行快速排序即可, 枢纽不用再排序
            三项取中
                从数组中取 最左,中间,最右 三个数据比较, 选取中间值作为枢纽
                可避免选取的枢纽正好为最大值或最小值造成效率最差的情况
                遵循选取最右边的值作为标志值的规则,将枢纽放到最右
        效率
            划分
                比较次数: n次左右
                交换次数: 平均需要n/4次
                总效率:   O(N)
        实现
            /**
             * @描述:快速排序
             * @开发人员:likaihao
             * @开发时间:2016年8月1日 下午8:33:32
             * @param arr
             */
            public static void quickSort(int[] arr){
                quickSort2(arr,0,arr.length-1,1);
            }
            /**
             * @描述:快速排序的递归方法
             * @开发人员:likaihao
             * @开发时间:2016年8月1日 下午8:35:33
             * @param arr 要排序的数组
             * @param begin 开始索引
             * @param end 结束索引
             * @param depth 层级数,用于打印日志
             */
            private static void quickSort2(int[] arr, int begin, int end,int depth){
                String spaceStr = "                    ";
                System.out.println(spaceStr.substring(2,depth*2)+"- 第"+depth + "层,划分开始,数组区间为:"+getArrPrintStr(arr,begin,end));
                if(end-begin<=0){
                    //如果长度小于等于1,无需比较
                }else{
                    //三项取中的方法,获取枢纽值(会将枢纽值放到最arr[end]的位置)
                    medianOfThree(arr,begin,end);
                    //划分,获得枢纽位置
                    int pivotIndex = partition(arr,begin,end);
                    System.out.println(spaceStr.substring(2,depth*2)+"- 第"+depth + "层,划分完成,数组区间为:"+getArrPrintStr(arr,begin,end)+",枢纽:"+arr[pivotIndex]);
                    //两边分别进行快速排序
                    quickSort2(arr,begin,pivotIndex-1, depth+1);
                    quickSort2(arr,pivotIndex+1,end, depth+1);
                }
                System.out.println(spaceStr.substring(2,depth*2)+"* 第"+depth + "层,排序完成,数组区间为:"+getArrPrintStr(arr,begin,end));
            }
            /**
             * @描述:三项取中,将三项数据进行排序,完成后将最大的项和中间的项换位置,保持中间值(枢纽)在最右,最小值在最左
             * @开发人员:likaihao
             * @开发时间:2016年8月1日 下午8:39:23
             * @param arr 数组
             * @param begin 开始索引
             * @param end 结束索引
             */
            private static void medianOfThree(int[] arr, int begin, int end){
                int diff = end-begin;
                if(diff<2){
                    //长度小于3,不用排序
                    return;
                }else{
                    //长度大于3,取中间一个数据, 将左中右三个数据排序
                    int center = (begin + end)/2;
                    if(arr[begin] > arr[center]){
                        swap(arr,begin,center);
                    }
                    if(arr[center] > arr[end]){
                        swap(arr,center,end);
                    }
                    if(arr[begin] > arr[center]){
                        swap(arr,begin,center);
                    }
                    //将最大和中间的换位置
                    swap(arr,center,end);
                }
            }
            /**
             * @描述:数组交换位置
             * @开发人员:likaihao
             * @开发时间:2016年8月1日 下午8:53:21
             * @param arr
             * @param index1
             * @param index2
             */
            public static void swap(int[] arr, int index1, int index2){
                int temp = arr[index1];
                arr[index1] = arr[index2];
                arr[index2] = temp;
            }
            /**
             * @描述:对数组按指定数值进行划分(小的在左,大的在右),采用最右边的值为枢纽,并返回枢纽的位置
             * @开发人员:likaihao
             * @开发时间:2016年8月2日 上午9:43:20
             * @param arr 数组
             * @param start 开始索引
             * @param end 结束索引
             * @return 枢纽的位置
             */
            public static int partition(int[] arr, int start, int end){
                int pivot = arr[end]; //采用最右边的值为枢纽
                int left = start;
                int right = end-1; //最右边的枢纽不用参与比较,最后要将枢纽移动到正确的位置上(两组之间)
                
                //移动left和right指针,直到left>right
                while(left < right){
                    //查找左边比pivot大的项 
                    //left<=right可防止下标越界 
                    while(left <= right && arr[left] < pivot){
                        left ++;
                    }
                    
                    //查找右边比pivot小的项
                    while(left <= right && arr[right] > pivot){
                        right --;
                    }
                    
                    //如果left<right,说明left碰到了比标志值大的项,right碰到了比标志值小的项,交换位置
                    if(left < right){
                        swap(arr,left,right);
                        
                        //当前位置都判断过了
                        left++;
                        right--;
                    }
                }
                //将枢纽放到 后一组的最左边,并返回枢纽的索引
                int pivotIndex = right+1;
                if(pivotIndex!=end){
                    swap(arr,pivotIndex,end);
                }
                return pivotIndex;
            }
            //获得arr的打印字符串
            public static String getArrPrintStr(int[] arr,int start,int end){
                StringBuilder builder = new StringBuilder();
                builder.append("[");
                for(int i=start;i<=end;i++){
                    builder.append(arr[i]);
                    builder.append(",");
                }
                if(builder.length()>1){
                    builder.deleteCharAt(builder.length()-1);
                }
                builder.append("]");
                return builder.toString();
            }


















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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值