zoukankan      html  css  js  c++  java
  • 入门学算法_堆排序、树遍历、生产消费模型

    程序员的基本功包括数据结构与算法、操作系统、数据库原理、网络等等,这些提升内功的东西是需要花费长时间去练习的,也只有这些东西才是阻碍发展的瓶颈。

    工作了几年就会发现,技术框架更新迭代非常快,框架是学不完的,但是框架的底层原理都是相同的,都是由基础衍生出来的,今天我继续之前的学习算法之路来看看基本功中排序算法的堆排序、二叉树的遍历与生产者消费者模型。

    • 堆排序

       

    堆积排序(Heapsort)是指利用堆积树(堆)这种数据结构所设计的一种排序算法,可以利用数组的特点快速定位指定索引的元素。

    堆排序是不稳定的排序方法,辅助空间为O(1), 最坏时间复杂度为O(nlog2n) ,堆排序的堆序的平均性能较接近于最坏性能。

    堆排序利用了大根堆(或小根堆)堆顶记录的关键字最大(或最小)这一特征,使得在当前无序区中选取最大(或最小)关键字的记录变得简单。

    • 用大根堆排序的基本思想

    1. 先将初始文件 R[1..n] 建成一个大根堆,此堆为初始的无序区

    2. 再将关键字最大的记录 R[1] (即堆顶)和无序区的最后一个记录 R[n] 交换,由此得到新的无序区 R[1..n-1] 和有序区 R[n] ,且满足 R[1..n-1].keys≤R[n].key

    3. 由于交换后新的根 R[1] 可能违反堆性质,故应将当前无序区 R[1..n-1] 调整为堆。然后再次将 R[1..n-1] 中关键字最大的记录 R[1] 和该区间的最后一个记录 R[n-1] 交换,由此得到新的无序区 R[1..n-2] 和有序区 R[n-1..n],且仍满足关系 R[1..n-2].keys ≤ R[n-1..n].keys,同样要将R[1..n-2]调整为堆。如此循环……

    4. 直到无序区只有一个元素为止。

    • 大根堆排序算法的基本操作:

    1. 初始化操作:将 R[1..n] 构造为初始堆;

    2. 每一趟排序的基本操作:将当前无序区的堆顶记录 R[1] 和该区间的最后一个记录交换,然后将新的无序区调整为堆(亦称重建堆)。

      注意:

      1. 只需做n-1趟排序,选出较大的 n-1 个关键字即可以使得文件递增有序。

      2. 用小根堆排序与利用大根堆类似,只不过其排序结果是递减有序的。堆排序和直接选择排序相反:在任何时刻堆排序中无序区总是在有序区之前,且有序区是在原向量的尾部由后往前逐步扩大至整个向量为止。

    public class HeapSortTest {   
        public static void main(String[] args) {  
            int[] data5 = new int[] { 5, 3, 6, 2, 1, 9, 4, 8, 7 };  
            print(data5);  
            heapSort(data5);  
            System.out.println("排序后的数组:");  
            print(data5);  
        }  
        //交换数组位置
        public static void swap(int[] data, int i, int j) {  
            if (i == j) {  
                return;  
            }  
            data[i] = data[i] + data[j];  
            data[j] = data[i] - data[j];  
            data[i] = data[i] - data[j];  
        }  
      //堆排序调用
        public static void heapSort(int[] data) {  
            for (int i = 0; i < data.length; i++) {  
                createMaxdHeap(data, data.length - 1 - i);  
                swap(data, 0, data.length - 1 - i);  
                print(data);  
            }  
        }  
      //创建最大堆
        public static void createMaxdHeap(int[] data, int lastIndex) {  
            for (int i = (lastIndex - 1) / 2; i >= 0; i--) {  
                // 保存当前正在判断的节点  
                int k = i;  
                // 若当前节点的子节点存在  
                while (2 * k + 1 <= lastIndex) {  
                    // biggerIndex总是记录较大节点的值,先赋值为当前判断节点的左子节点  
                    int biggerIndex = 2 * k + 1;  
                    if (biggerIndex < lastIndex) {  
                        // 若右子节点存在,否则此时biggerIndex应该等于 lastIndex  
                        if (data[biggerIndex] < data[biggerIndex + 1]) {  
                            // 若右子节点值比左子节点值大,则biggerIndex记录的是右子节点的值  
                            biggerIndex++;  
                        }  
                    }  
                    if (data[k] < data[biggerIndex]) {  
                        // 若当前节点值比子节点最大值小,则交换2者得值,交换后将biggerIndex值赋值给k  
                        swap(data, k, biggerIndex);  
                        k = biggerIndex;  
                    } else {  
                        break;  
                    }  
                }  
            }  
        }  
      
        public static void print(int[] data) {  
            for (int i = 0; i < data.length; i++) {  
                System.out.print(data[i] + "	");  
            }  
            System.out.println();  
        }  
    }
    public class HeapSortTest {       public static void main(String[] args) {          int[] data5 = new int[] { 5, 3, 6, 2, 1, 9, 4, 8, 7 };          print(data5);          heapSort(data5);          System.out.println("排序后的数组:");          print(data5);      }      //交换数组位置    public static void swap(int[] data, int i, int j) {          if (i == j) {              return;          }          data[i] = data[i] + data[j];          data[j] = data[i] - data[j];          data[i] = data[i] - data[j];      }    //堆排序调用    public static void heapSort(int[] data) {          for (int i = 0; i < data.length; i++) {              createMaxdHeap(data, data.length - 1 - i);              swap(data, 0, data.length - 1 - i);              print(data);          }      }    //创建最大堆    public static void createMaxdHeap(int[] data, int lastIndex) {          for (int i = (lastIndex - 1) / 2; i >= 0; i--) {              // 保存当前正在判断的节点              int k = i;              // 若当前节点的子节点存在              while (2 * k + 1 <= lastIndex) {                  // biggerIndex总是记录较大节点的值,先赋值为当前判断节点的左子节点                  int biggerIndex = 2 * k + 1;                  if (biggerIndex < lastIndex) {                      // 若右子节点存在,否则此时biggerIndex应该等于 lastIndex                      if (data[biggerIndex] < data[biggerIndex + 1]) {                          // 若右子节点值比左子节点值大,则biggerIndex记录的是右子节点的值                          biggerIndex++;                      }                  }                  if (data[k] < data[biggerIndex]) {                      // 若当前节点值比子节点最大值小,则交换2者得值,交换后将biggerIndex值赋值给k                      swap(data, k, biggerIndex);                      k = biggerIndex;                  } else {                      break;                  }              }          }      }      public static void print(int[] data) {          for (int i = 0; i < data.length; i++) {              System.out.print(data[i] + "	");          }          System.out.println();      }  }
    • 树的遍历(深度优先、广度优先)

    在编程生活中,我们总会遇见树性结构,这几天刚好需要对树形结构操作,就记录下自己的操作方式以及过程。现在假设有一颗这样树,(是不是二叉树都没关系,原理都是一样的)

     

    深度优先

    英文缩写为 DFS 即 Depth First Search.其过程简要来说是对每一个可能的分支路径深入到不能再深入为止,而且每个节点只能访问一次。对于上面的例子来说深度优先遍历的结果就是:A,B,D,E,I,C,F,G,H.(假设先走子节点的的左侧)。

    深度优先遍历各个节点,需要使用到堆(Stack)这种数据结构。Stack的特点是是先进后出。

    整个遍历过程如下:

    1. 首先将A节点压入堆中,stack(A);

    2. 将A节点弹出,同时将A的子节点C,B压入堆中,此时B在堆的顶部,stack(B,C);

    3. 将B节点弹出,同时将B的子节点E,D压入堆中,此时D在堆的顶部,stack(D,E,C);

    4. 将D节点弹出,没有子节点压入,此时E在堆的顶部,stack(E,C);

    5. 将E节点弹出,同时将E的子节点I压入,stack(I,C);

    6. ...依次往下,最终遍历完成。Java 代码如下:

    public void depthFirst() {
    
        Stack<Map<String, Object>> nodeStack = new Stack<Map<String, Object>>();
        //节点使用Map存放
        Map<String, Object> node = new HashMap<String, Object>();
        
        nodeStack.add(node);
        
        while (!nodeStack.isEmpty()) {
        
            node = nodeStack.pop();
            
            System.out.println(node);
            //获得节点的子节点,对于二叉树就是获得节点的左子结点和右子节点
            
            List<Map<String, Object>> children = getChildren(node);
            if (children != null && !children.isEmpty()) {
                for (Map child : children) {
                    nodeStack.push(child);
                }
            }
        }
    }
     

    广度优先

    英文缩写为 BFS 即 Breadth FirstSearch。其过程检验来说是对每一层节点依次访问,访问完一层进入下一层,而且每个节点只能访问一次。对于上面的例子来说,广度优先遍历的 结果是:A,B,C,D,E,F,G,H,I(假设每层节点从左到右访问)。

    广度优先遍历各个节点,需要使用到队列(Queue)这种数据结构,

    Queue 的特点是先进先出,其实也可以使用双端队列,区别就是双端队列首位都可以插入和弹出节点。整个遍历过程如下:

    1. 首先将A节点插入队列中,queue(A);

    2. 将A节点弹出,同时将A的子节点B,C插入队列中,此时B在队列首,C在队列尾部,queue(B,C);

    3. 将B节点弹出,同时将B的子节点D,E插入队列中,此时C在队列首,E在队列尾部,queue(C,D,E);

    4. 将C节点弹出,同时将C的子节点F,G,H插入队列中,此时D在队列首,H在队列尾部,queue(D,E,F,G,H);

    5. 将D节点弹出,D没有子节点,此时E在队列首,H在队列尾部,queue(E,F,G,H);

    6. ...依次往下,最终遍历完成,Java 代码如下:

    public void breadthFirst() {
    //这里使用的是双端队列,和使用queue是一样的
        Deque<Map<String, Object>> nodeDeque = new ArrayDeque<Map<String, Object>>();
        
        Map<String, Object> node = new HashMap<String, Object>();
        
        nodeDeque.add(node);
       
        while (!nodeDeque.isEmpty()) {
            
            node = nodeDeque.peekFirst();
            
            System.out.println(node);
            
            //获得节点的子节点,对于二叉树就是获得节点的左子结点和右子节点
            List<Map<String, Object>> children = getChildren(node);
            if (children != null && !children.isEmpty()) {
                for (Map child : children) {
                    nodeDeque.add(child);
                }
            }
        }
    }
     
    • 生产者消费者模型

    重点使用 BlockingQueue,它也是 java.util.concurrent 下的主要用来控制线程同步的工具。

    BlockingQueue 有四个具体的实现类,根据不同需求,选择不同的实现类

    1)ArrayBlockingQueue:一个由数组支持的有界阻塞队列,规定大小的 BlockingQueue,其构造函数必须带一个 int 参数来指明其大小.其所含的对象是以 FIFO (先入先出)顺序排序的。

    2)LinkedBlockingQueue:大小不定的 BlockingQueue,若其构造函数带一个规定大小的参数,生成的BlockingQueue有大小限制,若不带大小参数,所生成的 BlockingQueue 的大小由Integer.MAX_VALUE来决定.其所含的对象是以 FIFO (先入先出)顺序排序的。

    LinkedBlockingQueue 可以指定容量,也可以不指定,不指定的话,默认最大是Integer.MAX_VALUE,其中主要用到 put 和take方法,put 方法在队列满的时候会阻塞直到有队列成员被消费,take 方法在队列空的时候会阻塞,直到有队列成员被放进来。

    3)PriorityBlockingQueue:类似于LinkedBlockQueue,但其所含对象的排序不是FIFO,而是依据对象的自然排序顺序或者是构造函数的Comparator 决定的顺序。

    4)SynchronousQueue:特殊的 BlockingQueue,对其的操作必须是放和取交替完成的。

    //生产者:
    import java.util.concurrent.BlockingQueue;  
    
    public class Producer implements Runnable {  
    
        BlockingQueue<String> queue;  
        
        public Producer(BlockingQueue<String> queue) {  
            this.queue = queue;  
        }  
        
        @Override  
        public void run() {  
            try {  
                String temp = "A Product, 生产线程:"  
                        + Thread.currentThread().getName();  
                System.out.println("I have made a product:"  
                        + Thread.currentThread().getName());  
               //如果队列是满的话,会阻塞当前线程         
                queue.put(temp); 
                 
            } catch (InterruptedException e) {  
                e.printStackTrace();  
            }  
        }  
    }  
    
    //消费者:
    
    import java.util.concurrent.BlockingQueue;  
    
    public class Consumer implements Runnable{  
       
        BlockingQueue<String> queue;  
          
        public Consumer(BlockingQueue<String> queue){  
            this.queue = queue;  
        }  
        
        @Override  
        public void run() {  
            try {  
                //如果队列为空,会阻塞当前线程  
                String temp = queue.take();
                
                System.out.println(temp);  
            } catch (InterruptedException e) {  
                e.printStackTrace();  
            }  
        }  
       }
    
    //测试类:
    
    import java.util.concurrent.ArrayBlockingQueue;  
    import java.util.concurrent.BlockingQueue;  
    import java.util.concurrent.LinkedBlockingQueue;  
      
    public class Test3 {  
      
        public static void main(String[] args) {  
            
            BlockingQueue<String> queue = new LinkedBlockingQueue<String>(2);  
            // BlockingQueue<String> queue = new LinkedBlockingQueue<String>();  
            //不设置的话,LinkedBlockingQueue默认大小为Integer.MAX_VALUE  
              
            // BlockingQueue<String> queue = new ArrayBlockingQueue<String>(2);  
      
            Consumer consumer = new Consumer(queue);  
            Producer producer = new Producer(queue);  
            
            for (int i = 0; i < 5; i++) {  
                new Thread(producer, "Producer" + (i + 1)).start();  
      
                new Thread(consumer, "Consumer" + (i + 1)).start();  
            }  
        }  
    }
     

    由于队列的大小限定成了 2,所以最多只有两个产品被加入到队列当中,而且消费者取到产品的顺序也是按照生产的先后顺序,原因就是LinkedBlockingQueue 和 ArrayBlockingQueue 都是按照 FIFO 的顺序存取元素的。

  • 相关阅读:
    LeetCode153 Find Minimum in Rotated Sorted Array. LeetCode162 Find Peak Element
    LeetCode208 Implement Trie (Prefix Tree). LeetCode211 Add and Search Word
    LeetCode172 Factorial Trailing Zeroes. LeetCode258 Add Digits. LeetCode268 Missing Number
    LeetCode191 Number of 1 Bits. LeetCode231 Power of Two. LeetCode342 Power of Four
    LeetCode225 Implement Stack using Queues
    LeetCode150 Evaluate Reverse Polish Notation
    LeetCode125 Valid Palindrome
    LeetCode128 Longest Consecutive Sequence
    LeetCode124 Binary Tree Maximum Path Sum
    LeetCode123 Best Time to Buy and Sell Stock III
  • 原文地址:https://www.cnblogs.com/fanyi0922/p/11144353.html
Copyright © 2011-2022 走看看