Data structure and algorithm (2): sorting (recursion, backtracking, number theory, insertion, Hill, merge, selection, bubbling, quick sort, greedy, dynamic programming)

Algorithm sorting: recursion, backtracking, number theory, insertion, Hill, merge, selection, bubbling, quick sort, greedy, dynamic programming

Number theory thinking: use mathematical formulas or theorems or laws to solve problems;

The most difficult point in algorithm thinking: recursion + dynamic programming; tree theory: binary tree, red-black tree

Thinking questions:

  1. There is a rebate in the WeChat distribution system. Everyone should know that, for example, B is A’s offline, and C is B’s offline. Then A can share the money of B and C during the rebate. At this time, do we just To find the last superior of B and C respectively. How do we usually solve this problem?

  2. Fibonacci sequence: 1 1 2 3 5 8 13 21 What are the characteristics? Starting from the third number is equal to the addition of the previous two numbers; solution formula: f(n)=f(n-1)+f(n-2) termination condition: n<=2 f(n)=1

recursion

recursive definition

For example, there are too many people queuing up at a certain window, and I don’t know where I am, so I ask the person in front of me who is in line, because I know where I am when I know who is in line
. But the people in front don't know where they rank, so what should they do? He can also continue to ask until the first person is asked, and then from the first person to me, I will know exactly which number I am. The above scenario is a typical recursion. Did you find a rule in this process? Then there will be a process of asking, and there will be a process of returning after asking the first one. This is pass (ask) plus return (return). So can we use a mathematical formula to solve this process? So what is this mathematical formula?

f(n)=f(n-1)+1

f(n): indicates my position

f(n-1): means the person in front of me;

call yourself

Recursive application scenarios

  1. 一个问题的解可以分解为几个子问题的解: Sub-problem, we can decompose a large-scale data problem into many small problems through the idea of ​​​​divide and conquer.
    We can regard the person who asked the question just now as a sub-question. Big to small

  2. This problem and the sub-problems after decomposition,求解思路完全一样

  3. 一定有一个最后确定的答案,即递归的终止条件: The question just now was the first person. The first person must know where he is ranked, that is, when n=1, if there is no such feature, then our recursion will have an infinite loop, and finally the program will overflow the stack; stack out of

Time and Space Complexity Analysis of Recursion

Take the Fibonacci sequence as an example to analyze the recursive tree: f(n)=f(n-1)+f(n-2)

Both time complexity and space complexity: O(2^n)=>O(n) or O(nlogn)

recursive optimization

insert image description here

  1. Use non-recursive: All recursive code can theoretically be converted to non-recursive
  2. Join the cache: save the results of our middle operations, so that the recursion can be reduced to o(n)
  3. Tail recursion: what is tail recursion? Tail recursion means that the calling function must appear at the end, without any other operations. Because when our compiler compiles the code, if it finds that there is no operation at the end of the function, it will not create a new stack at this time and overwrite it to the front. Counting backwards, there is no need to backtrack, because we will bring down the intermediate results every time.
// 斐波纳契/微信分销系统
// 1 1 2 3 5 8 13
// f(n) = f(n-1) + f(n-2)
// 递归
// 递归优化
// 1. 使用非递归
// 2. 加入缓存
// 3. 尾递归
// 递归注意事项----栈溢出和时间问题
public class FibonacciSeq {
    
    

    //递归 40 : 102334155 耗时:316 ms
    public static int fab(int n) {
    
     // 时间复杂度/空间复杂度 O(2^n) => 如何优化
        if (n <= 2) return 1;
        return fab(n - 1) + fab(n - 2);
    }

    //尾递 第一个优化 40 : 102334155 耗时:0 ms
    public static int noFab(int n) {
    
     // 不使用递归-节约空间
        if (n <= 2) return 1;
        int a = 1;
        int b = 1;
        int c = 0;
        for (int i = 3; i <= n; i ++) {
    
    
            c = a + b;
            a = b;
            b = c;
        }
        return c;
    }

    //尾递 40 : 102334155 耗时:0 ms
    public static int noFabSimple(int data[], int n) {
    
     // 不使用递归--占用空间
        if (n <= 2) return 1;
        data[1] = 1;
        data[2] = 1;
        for (int i = 3; i <= n; i ++) {
    
    
            data[i] = data[i - 1] + data[i - 2];
        }
        return data[n];
    }

    //尾递 40 : 102334155 耗时:0 ms
    public static int fab2(int data[], int n) {
    
    
        if (n <= 2) return 1;
        if (data[n] > 0) return data[n];

        int res = fab2(data,n-1) + fab2(data,n-2);
        data[n] = res;
        return res;
    }

    //尾递: 改成尾递归 prepre 上上一次运行结果  pre 上次运行结果
    public static int tailFab2(int n, int prepre, int pre) {
    
    
        if (n <= 2) return pre;
        return tailFab2(n - 1, pre, pre + prepre);
    }

    //求N的阶乘 用普通的递归怎么写 5=5*4*3*2*1 => f(n) = n * f(n-1)
    public static int fac(int n) {
    
    
        if (n <= 1) return 1;
        return n * fac(n - 1);
    }

    //改成尾递归 求N的阶乘 用普通的递归怎么写 5=5*4*3*2*1 => f(n) = n * f(n-1)
    public static int tailFac(int n, int res) {
    
    //尾递归
        if (n <= 1) return res;
        return tailFac(n-1, n * res);
    }


    public static void main(String[] args) {
    
    
//        for (int i = 1; i <= 40; i++) {
    
    
//            long start = System.currentTimeMillis();
//            System.out.println(i + " : " + fab(i) + " 耗时:" + (System.currentTimeMillis() - start) + " ms");
//        }
//
//        for (int i = 1; i <= 40; i++) {
    
    
//            long start = System.currentTimeMillis();
//            System.out.println(i + " : " + noFab(i) + " 耗时:" + (System.currentTimeMillis() - start) + " ms");
//        }
//
//        data = new int[41];
//        for (int i = 1; i <= 40; i++) {
    
    
//            long start = System.currentTimeMillis();
//            System.out.println(i + " : " + noFabSimple(data, i) + " 耗时:" + (System.currentTimeMillis() - start) + " ms");
//        }
//
//        data = new int[41];
//        for (int i = 1; i <= 40; i++) {
    
    
//            long start = System.currentTimeMillis();
//            System.out.println(i + " : " + fab2(data, i) + " 耗时:" + (System.currentTimeMillis() - start) + " ms");
//        }
//
//        for (int i = 1; i <= 40; i++) {
    
    
//            long start = System.currentTimeMillis();
//            System.out.println(i + " : " + tailFab2(i, 1, 1) + " 耗时:" + (System.currentTimeMillis() - start) + " ms");
//        }
//
//
//        for (int i = 1; i <= 10; i++) {
    
    
//            long start = System.currentTimeMillis();
//            System.out.println(i + " : " + tailFac(i, 1) + " 耗时:" + (System.currentTimeMillis() - start) + " ms");
//        }
//
//        for (int i = 1; i <= 10; i++) {
    
    
//            long start = System.currentTimeMillis();
//            System.out.println(i + " : " + fac(i) + " 耗时:" + (System.currentTimeMillis() - start) + " ms");
//        }
    }
}
/**
 递归
 1 : 1 耗时:0 ms
 2 : 1 耗时:0 ms
 3 : 2 耗时:0 ms
 4 : 3 耗时:0 ms
 5 : 5 耗时:0 ms
 6 : 8 耗时:0 ms
 7 : 13 耗时:0 ms
 8 : 21 耗时:0 ms
 9 : 34 耗时:0 ms
 10 : 55 耗时:0 ms
 11 : 89 耗时:0 ms
 12 : 144 耗时:0 ms
 13 : 233 耗时:0 ms
 14 : 377 耗时:0 ms
 15 : 610 耗时:0 ms
 16 : 987 耗时:1 ms
 17 : 1597 耗时:0 ms
 18 : 2584 耗时:0 ms
 19 : 4181 耗时:0 ms
 20 : 6765 耗时:0 ms
 21 : 10946 耗时:0 ms
 22 : 17711 耗时:0 ms
 23 : 28657 耗时:1 ms
 24 : 46368 耗时:0 ms
 25 : 75025 耗时:0 ms
 26 : 121393 耗时:1 ms
 27 : 196418 耗时:1 ms
 28 : 317811 耗时:1 ms
 29 : 514229 耗时:1 ms
 30 : 832040 耗时:4 ms
 31 : 1346269 耗时:4 ms
 32 : 2178309 耗时:7 ms
 33 : 3524578 耗时:11 ms
 34 : 5702887 耗时:17 ms
 35 : 9227465 耗时:30 ms
 36 : 14930352 耗时:50 ms
 37 : 24157817 耗时:90 ms
 38 : 39088169 耗时:145 ms
 39 : 63245986 耗时:242 ms
 40 : 102334155 耗时:316 ms

不使用递归
 0 : 1 耗时:0 ms
 1 : 1 耗时:0 ms
 2 : 1 耗时:0 ms
 3 : 2 耗时:0 ms
 4 : 3 耗时:0 ms
 5 : 5 耗时:0 ms
 6 : 8 耗时:0 ms
 7 : 13 耗时:0 ms
 8 : 21 耗时:0 ms
 9 : 34 耗时:0 ms
 10 : 55 耗时:0 ms
 11 : 89 耗时:0 ms
 12 : 144 耗时:0 ms
 13 : 233 耗时:0 ms
 14 : 377 耗时:0 ms
 15 : 610 耗时:0 ms
 16 : 987 耗时:0 ms
 17 : 1597 耗时:0 ms
 18 : 2584 耗时:0 ms
 19 : 4181 耗时:0 ms
 20 : 6765 耗时:0 ms
 21 : 10946 耗时:0 ms
 22 : 17711 耗时:0 ms
 23 : 28657 耗时:0 ms
 24 : 46368 耗时:0 ms
 25 : 75025 耗时:0 ms
 26 : 121393 耗时:0 ms
 27 : 196418 耗时:0 ms
 28 : 317811 耗时:0 ms
 29 : 514229 耗时:0 ms
 30 : 832040 耗时:0 ms
 31 : 1346269 耗时:0 ms
 32 : 2178309 耗时:0 ms
 33 : 3524578 耗时:0 ms
 34 : 5702887 耗时:0 ms
 35 : 9227465 耗时:0 ms
 36 : 14930352 耗时:0 ms
 37 : 24157817 耗时:0 ms
 38 : 39088169 耗时:0 ms
 39 : 63245986 耗时:0 ms
 40 : 102334155 耗时:0 ms
 */

Sort performance analysis

  1. Time efficiency: determines how long the algorithm runs, O(1)
  2. space complexity
  3. Comparisons & Exchanges
  4. Stability After 1 9 *3 5 3 第一种:1 *3 3 5 9 第二种:1 3 *3 5 9the same two numbers are sorted, the relative position remains unchanged. What is the point of a stable sort? Where is the application? For example, e-commerce order sorting (from small to large, with the same amount according to the order time)

insertion sort

insert image description here

Look at the following example: Insertion sort on 7 8
9 0 4 3 9




 public static int[] insertSort(int arr[]) {
    
    
     for (int i = 1; i < arr.length; i++) {
    
    
         int curr = arr[i];
         int pre = i - 1;
         for (; pre >= 0 ; pre--) {
    
    
             if (curr < arr[pre]) {
    
    
                 arr[pre+1] = arr[pre];
             } else {
    
    
                 break;
             }
         }
         arr[pre+1] = curr;
     }
     return arr;
 }

Hill sort

Incremental segmentation: add=n/2 n=10 =>5,2,1

   public static void shellSort(int data[]) {
    
    
       int n = data.length;

       for (int add = n/2; add >= 1 ; add/=2) {
    
    
           for (int i = add; i < n; i++) {
    
    
               int temp = data[i];
               int j = i - add;
               for (; j >= 0; j-=add) {
    
    
                   if (data[j] > temp) {
    
    
                       data[j + add] = data[j];
                   } else {
    
    
                       break;
                   }
               }
               data[j+add] = temp;
           }
       }
   }

merge sort

insert image description here


    public static void mergeSort(int[] arr, int left, int right) {
    
    
        if (left < right) {
    
    
            int mid = (left + right)/2;
            mergeSort(arr, left, mid);
            mergeSort(arr, mid+1, right);
            merge(arr, left, mid, right);
        }
    }

    public static void merge(int[] arr, int left, int mid, int right) {
    
    
        int temp[] = new int[arr.length];

        int leftPoint = left;
        int rightPoint = mid + 1;

        int cur = left; //当前位置

        while (leftPoint <= mid && rightPoint <= right) {
    
    
            if (arr[leftPoint] < arr[rightPoint]) {
    
    
                 temp[cur] = arr[leftPoint];
                 leftPoint++;
            } else {
    
    
                temp[cur] = arr[rightPoint];
                rightPoint++;
            }
            cur++;
        }

        while (leftPoint <= mid) {
    
    
            temp[cur++] = arr[leftPoint++];
        }

        while (rightPoint <= right) {
    
    
            temp[cur++] = arr[rightPoint++];
        }

        for (int i = left; i <= right; i++) {
    
    
            arr[i] = temp[i];
        }
    }

choose

The idea of ​​selection sorting is very similar to insertion sorting, and it is also divided into sorted and unsorted intervals. But the selection sort will find the smallest element from the unsorted interval every time, and put it at the end of the sorted interval. But unlike insertion sort, the array will be moved, selection sort will be exchanged every time

    public static int[] selectSort(int data[]) {
    
    
        for (int i = 0; i < data.length -1; i++) {
    
    
            int minloc = i;
            for (int j = i+1; j < data.length; j++) {
    
    
                if (data[j] < data[minloc]) {
    
    
                    minloc = j;
                }
            }
            int minTemp = data[minloc];
            data[minloc] = data[i];
            data[i] = minTemp;
        }
        return data;
    }

bubble

Core idea: Bubble sorting will only operate on two adjacent data. Each bubbling operation will compare two adjacent elements to see if they meet the size relationship requirements. If not, swap the two. One bubbling will move at least one element to where it should be, and repeat n times to complete the sorting of n data.

The result of the first bubbling: 4 5 6 3 2 1 -> 4 5 3 6 2 1 -> 4 5 3 2 6 1 -> 4 5 3 2 1 6, the position of which element is determined, 6 for the second
time The result of bubbling: 4 5 3 2 1 6 -> 4 3 5 2 1 6 -> 4 3 2 5 1 6 -> 4 3 2 1 5 6 The result of the third bubbling: 4 3 2 1 5
6- >3 4 2 1 5 6 -> 3 2 4 1 5 6 -> 3 2 1 4 5 6 The result of the
fourth bubble: 3 2 1 4 5 6 -> 2 3 1 4 5 6 -> 2 1 3 4 5 6
The result of the fifth bubbling: 2 1 3 4 5 6 -> 1 2 3 4 5 6

    public static int[] dubbleSort(int data[]) {
    
    
        for (int i = 0; i < data.length; i++) {
    
    
            for (int j = i+1; j < data.length; j++) {
    
    
                if (data[j] < data[i]) {
    
    
                    int temp = data[j];
                    data[j] = data[i];
                    data[i] = temp;
                }
            }
        }
        return data;
    }
    public static void dubbleSortTest(int arr[]) {
    
    
        for (int i = 0; i < arr.length-1; i++) {
    
    
            boolean flag = false;
            for (int j = 0; j < arr.length - 1 - i; j++) {
    
    
                if (arr[j] > arr[j+1]) {
    
    
                    int temp = arr[j];
                    arr[j] = arr[j+1];
                    arr[j+1] = temp;
                    flag = true;
                }
            }
            if (!flag) break;
        }
    }

quick row

insert image description here
Comparison of quicksort and merge:

  1. The processing of merge sort is bottom-up, first processing sub-problems, and then merging.
  2. Quick sorting is actually from top to bottom, partition first, and then deal with sub-problems without merging.
    The optimization is to optimize the benchmark number and provide an idea of ​​taking the middle of the three numbers.

45 28 80 90 50 16 100 10
Benchmark number: Generally, it is the first one of the sequence to be sorted.
The first sorting reference number: 45
Find the number smaller than the reference number from the back to the front and swap:
*10 28 80 90 50 16 100 *45
Find the number larger than the reference number from the front to the back and swap:
10 28 * 45 90 50 16 100 *80
. . .
Divide it into 3 parts by the reference number, the ratio on the left is smaller, and the ratio on the right is larger:
{10 28 16} 45 {50 90 100 80}
This is the first time to complete the sorting with a 45-digit reference number.


    public static void quickSort(int data[], int left, int right) {
    
    
        int base = data[left]; //基准数

        int ll = left; //从左边找的位置
        int rr = right; //从右边找的位置

        while (ll < rr) {
    
    
            //从右边找比基数小的数
            while (ll < rr && data[rr] >= base) {
    
    
                rr--;
            }
            if (ll < rr) {
    
     //表示有找到比之大的
                 int temp = data[rr];
                 data[rr] = data[ll];
                 data[ll] = temp;
                 ll++;
            }

            while (ll < rr && data[ll] <= base) {
    
    
                ll++;
            }
            if (ll < rr) {
    
    
                int temp = data[rr];
                data[rr] = data[ll];
                data[ll] = temp;
                rr--;
            }

        }

        if (left < ll)
            quickSort(data, left, ll-1);

        if (ll < right)
            quickSort(data, ll+1, right);
    }

Compared

insert image description here

Greedy Algorithm

concept

Concept: The greedy algorithm is also called the greedy algorithm. When it solves a certain problem, it always makes the most immediate benefit.
That is to say, it only cares about the present and ignores the overall situation, so it is a local optimal solution. Core point: Deduce the global optimum through the local optimum

The routine of the greedy algorithm: there must be a sort. Huffman coding, greedy algorithm, compression algorithm. shortest path

thinking questions

1. One morning, the leader of the company asked you to solve a problem. Tomorrow, the company has N meetings of the same level that need to use the same meeting room. Now you are given the start and end time of the N meetings. How do you arrange the meeting room
? Maximum utilization? That is, the most sessions scheduled? For movies, it must have the most tickets and the highest admission rate. Comprehensive Algorithm

2. Double Eleven is coming soon, the goddess in Xiao C’s mind added N items to the shopping cart, and suddenly she won a prize to empty the shopping cart with 5,000 yuan of items (no change), each item only If she can buy one, how should she choose the item to maximize the winning amount? If there are multiple optimal combinations, you only need to give one. Hey, now the goddess is asking you, what should you do?

/**
 * 贪心算法
 * 最优
 * 最短
 * 最好
 *
 * 先按照开始时间排序, 之后按照当前开始时间,比较开始时间是否大于结束时间
 */
public class Meeting implements Comparable<Meeting> {
    
    

    int meNum;
    int startTime;
    int endTime;

    public Meeting(int meNum, int startTime, int endTime) {
    
    
        super();
        this.meNum = meNum;
        this.startTime = startTime;
        this.endTime = endTime;
    }

    @Override
    public int compareTo(Meeting o) {
    
    
        if (this.endTime > o.endTime) {
    
    
            return 1;
        }
        return -1;
    }

    @Override
    public String toString() {
    
    
        return "GreedySort{" +
                "meNum=" + meNum +
                ", startTime=" + startTime +
                ", endTime=" + endTime +
                '}';
    }

    /**
     * 4
     * 0 9
     * 8 10
     * 10 12
     * 8 20
     * GreedySort{meNum=1, startTime=0, endTime=9}
     * GreedySort{meNum=3, startTime=10, endTime=12}
     */
    public static void main(String[] args) {
    
    
        Scanner scanner = new Scanner(System.in);
        List<Meeting> meetings = new ArrayList<>();
        int n = scanner.nextInt(); //会议
        for (int i = 0; i < n; i++) {
    
    
            int start = scanner.nextInt();
            int end = scanner.nextInt();
            Meeting meeting = new Meeting(i+1, start, end);
            meetings.add(meeting);
        }
        meetings.sort(null);

        int curTime = 0; //当前时间,从一天的0点开始
        for (int i = 0; i < n; i++) {
    
    
            Meeting meeting = meetings.get(i);
            if (meeting.startTime >= curTime) {
    
    
                System.out.println(meeting.toString());
                curTime = meeting.endTime;
            }
        }
    }
}

dynamic programming

Thinking question-backpack problem: A thief went to a store to steal a backpack with a capacity of 50kg. Now he has the following items (the item cannot be divided, and there is only one item). How should the thief take it to get the maximum value?

thing weight value
Item 1 10kg 60 dollars
Item 2 20kg 100 yuan
Item 3 40kg 120 yuan

insert image description here

5kg bag

thing weight value
Item 1 1 6
Item 2 2 10
Item 3 4 12

Divide the 5kg bag into 1kg, calculate like this, the table inside indicates the most money that can be loaded under the current weight. The number columns in the table represent the items to be loaded

thing 1kg 2kg 3 kg 4kg 5kg
add item 1 6 6 6 6 6
add item 2 6 10 10+6=16 10+6=16 16
add item 3 6 10 16 16 18
/**
 * 背包算法
 * 购物车问题保存价值一样就可以
 */
public class Backpack {
    
    

    public static List<Integer> group(int dp[][], int good_list[]) {
    
    
        int value_max = dp[0].length - 1;
        int good_max = dp.length - 1;
        List<Integer> good_group = new ArrayList();
        while (value_max > 0 && good_max > 0) {
    
    
            if (dp[good_max][value_max] <= dp[good_max-1][value_max]) {
    
    
                good_max -= 1;
            } else {
    
    
                good_group.add(good_max);
                value_max -= good_list[good_max-1];
                good_max -= 1;
            }
        }
        return good_group;
    }

    public static int cart(int weight[], int lw) {
    
    
        int n = weight.length;
        int dp[][] = new int[n+1][lw+1]; //n表示物品、w表示重量,初始化全是0

        for (int i = 1; i <= n; i++) {
    
     //每次加的物品
            for (int w = 1; w <= lw; w++) {
    
    
                if (weight[i-1] <= w) {
    
     //当前物品重量小于分割重量 表示这个物品可以装进去
                    dp[i][w] = Math.max(weight[i-1] + dp[i-1][w-weight[i-1]], dp[i-1][w]);
                } else {
    
    
                    dp[i][w] = dp[i-1][w];
                }
            }
        }

        List<Integer> good_group = group(dp, weight);
        System.out.print("组合:");
        for (Integer integer : good_group) {
    
    
            System.out.print(integer + "\t");
        }
        System.out.println();

        return dp[n][lw];
    }

    public static int backpack(int value[], int weight[], int lw) {
    
    
        int n = weight.length;
        int dp[][] = new int[n+1][lw+1]; //n表示物品、w表示重量,初始化全是0

        for (int i = 1; i <= n; i++) {
    
     //每次加的物品
            for (int w = 1; w <= lw; w++) {
    
    
                if (weight[i-1] <= w) {
    
     //当前物品重量小于分割重量 表示这个物品可以装进去
                    dp[i][w] = Math.max(value[i-1] + dp[i-1][w-weight[i-1]], dp[i-1][w]);
                } else {
    
    
                    dp[i][w] = dp[i-1][w];
                }
            }
        }

        List<Integer> good_group = group(dp, weight);
        System.out.print("组合:");
        for (Integer integer : good_group) {
    
    
            System.out.print(integer + "\t");
        }
        System.out.println();

        return dp[n][lw];
    }

    /**
     * 组合:4	3	1
     * 8
     * 组合:3	1
     * 180
     */
    public static void main(String[] args) {
    
    
        System.out.println(cart(new int[]{
    
    1,2,3,4,5,9},8));

        System.out.println(backpack(new int[]{
    
    60, 100, 120},new int[]{
    
    10, 20, 30},40));
    }
}

Guess you like

Origin blog.csdn.net/menxu_work/article/details/130318770