Data Structure Enhanced Sweets - Sequence 1

Table of contents

tail recursion

question

introduce

features

principle

Answer

Array stack memory allocation

foreword

analyze

reanalysis

so-called multidimensional arrays

Application of the principle of program locality


tail recursion

question

  • In terms of space complexity, there is an O(n) example as follows:
void recur(int n) {
    if (n == 1) return;
    return recur(n - 1);
}
  • This is obviously a tail recursion, which is not optimized to O(1)

introduce

  • In traditional recursion, the typical model is to first perform the recursive call, then get the return value of the recursive call and calculate the result
  • In this way, you won't get the result of the calculation until each recursive call returns
  • This has two disadvantages:
  • Low efficiency, occupying memory
  • If the recursive chain is too long, statck overflow may occur
  • If a function calls itself (or another function that tail-calls itself, etc.) at the tail position, this is said to be tail-recursive
  • Tail recursion is also a special case of recursion
  • Tail recursion is a special kind of tail call, that is, a recursive function that directly calls itself at the end

features

  • The optimization of tail recursion is also the main reason to focus on tail calls
  • Tail recursion has two more features on the basis of ordinary tail calls:
  • What is called at the end is the function itself (Self-called);
  • It can be optimized so that the calculation only occupies a constant stack space (Stack Space)

principle

  • When the compiler detects that a function call is tail recursive, it overwrites the current active record instead of creating a new one on the stack
  • The compiler can do this, because the recursive call is the last statement to be executed in the current active period, so when this call returns, there is nothing else to do in the stack frame, so there is no need to save the stack frame
  • By overwriting the current stack frame instead of adding a new one on top of it, the stack space used is greatly reduced, which makes the actual operating efficiency higher

Answer

  • This code is tail recursive, theoretically the space complexity can be optimized to O(1)
  • However, most programming languages ​​(such as Java, Python, C++, Go, C#, etc.) do not support automatic optimization of tail recursion, so write O(n) here

Array stack memory allocation

foreword

  • Stack memory allocation is done automatically by the compiler, while heap memory is allocated by the programmer in the code (please note that the stack and heap here are not the stack and heap in the data structure)
  • 1. The stack is not flexible, and the allocated memory size cannot be changed; the heap is relatively flexible, and memory can be allocated dynamically;
  • 2. The stack is a relatively small piece of memory, which is prone to insufficient memory; the heap memory is large, but because it is dynamically allocated, it is easy to fragment, and it is more difficult and costly to manage the heap memory;
  • 3. Accessing the stack is faster than accessing the heap, because the stack memory is small and friendly to the cache, and the heap frame is scattered in a large space, and there will be more cache misses;

analyze

  • Assuming that at the beginning, the heap and stack are empty
  • 1 --- Declare the array:
  • int[] array = null;
  • array is just a declaration, it will open up a space on the stack, and the heap will open up space
  • 2---Create an array:
  • array = new int[10];
  • Create an array, open up space in the heap to store the array, and the array in the stack points to the storage space

  • 3---Assign a value to the array:
  • for(int i = 0; i < 10; i++) array[i]=i+1;

reanalysis

  • int[] a = {2,3,4}; 
  • int[] b = new int[4];
  • b = a;
  • // 定义Person类
    public class Person {                                                  
    	public int age;
    	public double height;
        public void info() {
                System.out.println("年龄:" + age + ",身高:" + height);
        }
    }
  • // 测试类
    public class ArrayTest {           
        public static void main(String[] args) {
    
            // 定义一个students数组变量,其类型是Person[]
            Person[] students = new Person[2];
    
            Person zhang = new Person();
            zhang.age = 10;
            zhang.height = 130;
    
            Person lee = new Person();
            lee.age = 20;
            lee.height = 180;
    
            //将zhang变量赋值给第一个数组元素
            students[0] = zhang;
            //将lee变量赋值给第二个数组元素
            students[1] = lee;
    
            //下面两行代码结果一样,因为lee和student[1]
            //指向的是同一个Person实例
            lee.info();
            students[1].info();
        }
    }
  • Person[] students = new Person[2] 时

  • Person zhang = new Person();
  • zhang.age = 10;
  • zhang.height = 130;
  • Person lee = new Person();
  • lee.age = 20;
  • lee.height = 180;

  • Assign the zhang variable to the first array element
  • students[0] = zhang;
  • Assign the lee variable to the second array element
  • students[1] = lee; 时

so-called multidimensional arrays

  • Let me talk about the conclusion first:
  • The Java language provides a syntax that supports multidimensional arrays
  • From the perspective of the underlying operating mechanism of the array, there is no multidimensional array!
  • Start explaining:
  • Arrays in Java are reference types, so an array variable is actually a reference
  • What are citations?
  • Reference = an alias; does not open up additional memory units; but occupies the same location in memory
  • What are reference types?
  • A value type stores its value directly while a reference type stores a reference to its value
  • This reference points to the real array memory, if the array element is also a reference type, that is, a multidimensional array
  • Does that end up pointing to the contents of the 1D array
  • for example:
  • Define a two-dimensional array and traverse it as a one-dimensional array
  • The result is null null null null,
  • Explain that a two-dimensional array is actually a reference type of elements in a one-dimensional array
  • i.e. an array of length 2
  • // 定义一个二维数组
    int[][] a;
    // 把a当作一维数组进行初始化,初始化a是一个长度为4的数组
    // a数组的元素又是引用类型
    a = new int[4][];
    // 把a当作一维数组进行遍历,遍历a的每个元素
    for (int i = 0; i < a.length; i++) {
        System.out.println(a[i]);// null null null null
    }
    // 初始化a的第一个元素
    a[0] = new int[2];
    // 访问a数组的第一个元素所指向数组的第二个元素
    a[0][1] = 6;
    // a数组的第一个元素是一个一维数组,遍历这个一维数组
    for (int i = 0; i < a[0].length; i++) {
        System.out.println(a[0][i]);
    }
  • int[][] a;
  • a = new int[4][]; when
  • a[0] = new int[2];
  • a[0][1] = 6; when
  • The program dynamically initializes the a[0] array,
  • Therefore, the system will assign 0 to each element of the array referenced by a[0] by default,
  • The program shows that the second element of a[0] is assigned a value of 6
  • Another example:
  • int[][] b = new int[3][4];
  • Simultaneously initialize both dimensions of a 2D array
  • The code defines a b array variable, which points to an array of length 3
  • The elements of this array are also an array type, and they each point to an int[] array with a corresponding length of 4
  • The value of each array element is 0

Application of the principle of program locality

  • In addition to the search performance linked list is not as good as the array
  • Another advantage is that the performance of arrays is higher than that of linked lists, that is, the principle of program locality
  • Here is a brief introduction, which belongs to the content of the composition principle
  • We know that the CPU runs very fast. If the CPU has to go to the memory to fetch data for every operation, it will undoubtedly be very time-consuming.
  • Therefore, a multi-level cache is often integrated between the CPU and the memory. The closer these caches are to the CPU, the faster the speed.
  • Therefore, if the data in the memory can be loaded into the L1, L2, and L3 caches in the figure below in advance, then the next time the CPU fetches data, it can be directly fetched from these caches, which can speed up the CPU execution speed.
  • Then under what circumstances will the data in the memory be loaded into the L1, L2, and L3 caches in advance?
  • The answer is that when an element is used, the elements near the element address will be loaded into the cache in advance
  • Take the integer array 1, 2, 3, 4 as an example
  • When the program uses the first element in the array (ie 1)
  • Since the CPU thinks that since 1 is used, the probability that the elements 2, 3, and 4 next to it will be used is very high, so it will add 2, 3, and 4 to the L1, L2, and L3 caches in advance, so that the CPU If 2, 3, and 4 are used when executing again, just fetch them directly from the L1, L2, and L3 caches, which can improve a lot of performance

Guess you like

Origin blog.csdn.net/weixin_59624686/article/details/130184230