算法 数据结构

291 阅读6分钟

希望看完这篇文章后,会 哦~~ 原来就个这呀~~~

1. 基础结构

数组

一段连续的物理内存区域,通过下标定位元素;

  • 查找时间复杂度:O(1);
  • 中间插入、移除数组中元素 时间复杂度 O(n);

链表

一种链式数据结构,由若干个节点组成,每个节点包含指向下一个节点的指针;

链表的物理存储方式是数据存储,访问方式是顺序访问。

查找时间复杂度:O(n);

中间插入、删除的时间复杂度:O(1);

汇总

常用的数据结构有很多;大多是以数组和链表为基础进行构建;因此数组和链表也可以叫做数据结构的物理结构;

2. 衍生结构

一种线性逻辑结构,可以用数组实现,也 可以用链表实现 ;先进后出原则;

队列

一种线性逻辑结构,可以用数组实现,也 可以用链表实现 ;先进先出原则;

哈希表

哈希表也叫散列表,是存储Key-Value映射的集合。对于某一个key, 哈希表可以在接近O(1) 的时间内进行读写操作。哈希表通过哈希函数实现 Key 和 数组下标的转化,通过开放地址法链地址法来解决哈希冲突;

写操作

先将key 利用哈希函数转化为 值,然后取模,定位到数组的位置,插入此处;

若此处已有值,

则采用开放地址法,在下一个没有被占用的地方插入;
或者采用链地址法,在此处的元素下,链接一个元素;

3. 树🌲

二叉树

树的一种特殊形式,顾名思义,这种树🌲每个节点最多有2个孩子节点。注:最多2个是指,可以是2个,也可以是1个,也可以没有;

结构如下所示:

满二叉树

一个二叉树的

  • 所有非叶子🍃节点都存在做孩子和右孩子;
  • 并且所有叶子🍃节点都在同一层级上;

那么这个🌲树就是满二叉树;

如下所示:

完全二叉树

对于有n个节点的二叉树,

  • 按层级顺序编号,则所有节点的编号为从1到n;
  • 若果这个树所有节点和同样深度的满二叉树的编号从1到n 的节点位置相同;

则这个二叉树为完全二叉树;

示例如下:

遍历

从宏观的角度来看,二叉树的遍历归结为两大类。

  1. 深度优先遍历(前序遍历、中序遍历、后续遍历);

2)广度优先遍历(层序遍历);

注:深度优先遍历的条件是,以根节点为基准的,且先左后右,不这样的话,遍历的种类有 3! = 6种了;

前序遍历

即,根节点在首位;根节点、左节点、右节点;

中序遍历

即,根节点在中间;左节点、根节点、右节点;

后续遍历

即,根节点在后边;左节点、右节点、根节点;

遍历源码(java版)

TreeNode 定义

/**
 *
 * 二叉树节点
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class TreeNode {

    private Integer data;

    private TreeNode leftNode;

    private TreeNode rightNode;

}

TreeTravese 遍历测试

/**
 * @desc 二叉树遍历
 * @author liuph
 * @date 2022/02/22 17:03
 */
public class TreeTraverse {


    /**
     *
     * <pre>
     * 创建一个二叉树,形式;
     *
     *
     *        1
     *       / \
     *      2   3
     *     / \   \
     *    4   5   6
     *  </pre>
     */
    public TreeNode createTree(){
        TreeNode treeNode4 = new TreeNode(4, null, null);
        TreeNode treeNode5 = new TreeNode(5, null, null);
        TreeNode treeNode6 = new TreeNode(6, null, null);


        TreeNode treeNode2 = new TreeNode(2, treeNode4, treeNode5);
        TreeNode treeNode3 = new TreeNode(3, null, treeNode6);
        return new TreeNode(1, treeNode2, treeNode3);
    }

    public void firstTraverse(TreeNode node){
        System.out.println("当前遍历节点:" + node.getData());

        TreeNode leftNode = node.getLeftNode();
        TreeNode rightNode = node.getRightNode();

        if(Objects.nonNull(leftNode)){
            firstTraverse(leftNode);
        }
        if(Objects.nonNull(rightNode)){
            firstTraverse(rightNode);
        }
    }

    public void middleTraverse(TreeNode node){
        TreeNode leftNode = node.getLeftNode();
        TreeNode rightNode = node.getRightNode();

        if(Objects.nonNull(leftNode)){
            middleTraverse(leftNode);
        }
        System.out.println("当前遍历节点:" + node.getData());

        if(Objects.nonNull(rightNode)){
            middleTraverse(rightNode);
        }
    }

    public void backTraverse(TreeNode node){
        TreeNode leftNode = node.getLeftNode();
        TreeNode rightNode = node.getRightNode();

        if(Objects.nonNull(leftNode)){
            backTraverse(leftNode);
        }
        if(Objects.nonNull(rightNode)){
            backTraverse(rightNode);
        }
        System.out.println("当前遍历节点:" + node.getData());
    }

    @Test
    public void traverseTest(){
        System.out.println("----------前序遍历----------");
        TreeNode tree = createTree();
        firstTraverse(tree);

        System.out.println("----------中序遍历----------");
        middleTraverse(tree);

        System.out.println("----------后序遍历----------");
        backTraverse(tree);
    }

}

\

二叉堆

一种完全二叉树,有两种:最大堆 和最小堆,可以用于创建优先队列(最大优先级队列、最小优先级队列);

最大堆特点:

  • 任何一个父节点,都不小于它任务子节点的值;

最小堆特点:

  • 任何一个父节点,都不大于它任务子节点的值;

物理存储:

一般使用数组进行存储,规则如下:
假设父节点为p, 左子节点 = 2p + 1, 右子节点 = 2p + 2;

堆排序

利用最大堆/最小堆的特性,就行排序;

示例:使用最大堆进行从小到大排序;

步骤:

  • 1)现将需要排序的数组转化成最大堆;
  • 2)取堆顶元素 与数组最后一位进行交换;
  • 3)将0-n-1 继续转化为最大堆,再次执行2)操作;
  • 4)重复2)、3),直到排序完成;

代码示例(java)

public class HeapSortTest{

    // 向下调整
    public static void down(int [] a ,int start ,int end ){
        // 定义父节点、左孩子
        int p = start;
        int l = 2 * p + 1;
        
        while(l < end){
            if(a[l] < a[l+1]){  // 取最大的子节点
                l++;
            }
            if(a[p] < a[l]){ // 比父节点大,交换
                int temp = a[p];
                a[p] = a[l];
                a[l] = temp;
            }
            p = l;
            l = 2 * l + 1;
        }
        
    }
    
    // 升序排
    public static void sortAsc(int [] a){
        for(int i = a.length/2 - 1; i >= 0  ; i--){
              down(a, i, a.length - 1);
        }
        
        // 逐步取最大堆 堆顶,置换完成排序
        for(int i = a.length - 1; i > 0 ; i --){
            int temp = a[0];
            a[0] = a[i];
            a[i] = temp;
            
            down(a,0,i - 1);
        }
    }


}

注:排序是为啥从 a.length - 2 开始呢,因为😄从队尾开始,保证每个元素都遍历,a.length = 2 * p + 2, p = a.length/2 - 1;

优先队列

此处优先队列基于二叉堆来实现,队列😄我们日常使用重点关注入队、出队,那么从这两个角度来说一下原理;

1)入队;

  • a. 默认也插入到数组尾部;
  • b. 做上浮操作(与 父节点 比较 ,大于父节点上浮);
  • c. 将父节点作为起始节点,再找父节点,上浮;
  • d. 重复b、c 完成入队;

2) 出队;

  • a. 弹出a[0] 元素;
  • b. 将最后一个元素,补齐到a[0];
  • c. a[0] 做下沉处理;

示例代码(java)

public class MaxPriorityQueue {
    // 默认定义数组长度为1024
    private int [] a = new int [1024];

    // 定义当前存储的位置
    private int currentIndex = 0;

    // 添加元素
    public void add(int value){
        a[currentIndex]=value;
        if(currentIndex > 0){
            // 上浮操作
            up();
        }
        currentIndex++;
    }

    // 对数组最后一个存储的值进行上浮操作
    public void up(){
        // 计算父节点  l = 2p + 1,  r = 2p + 2
        int p = currentIndex%2 == 0 ? (currentIndex-1)/2 : (currentIndex-2)/2;
        int currentTemp = currentIndex;

        while(p >= 0){
            if(a[p] < a[currentTemp]){ // 父节点 <  当前节点 ,交换
                int temp = a[p];
                a[p] = a[currentTemp];
                a[currentTemp] = temp;

                // 节点向上调整
                currentTemp = p;
                p = p%2 == 0 ? p/2 - 2 : p/2 -1;
            }else{
                break;
            }
        }
    }

    // 弹出元素,尾节点替换a[0],向下调整
    public int pop(){
        int value = a[0];
        a[0] = a[currentIndex - 1];
        a[currentIndex - 1] = 0;

        // 向下调整
        down();
        currentIndex--;
        return value;
    }

    // a[0] 向下调整
    public void down(){
        int p = 0;
        int l = 1;

        while(l < currentIndex){
            if(a[l] < a[l+1]){ // 取最大的子节点
                l++;
            }
            if(a[p] < a[l]){ // 父 < 最大子 ,替换
                int temp = a[p];
                a[p] = a[l];
                a[l] = temp;

                // 向下调整
                p = l;
                l = 2*l + 1;

            }else{
                break;
            }
        }
    }
    public void print(){
        for (int i = 0; i < currentIndex; i++) {
            System.out.print(a[i] + ",");
        }
    }

   @Test
   public void test(){
       MaxPriorityQueue priorityQueue = new MaxPriorityQueue();
       priorityQueue.add(6);
       priorityQueue.add(7);
       priorityQueue.add(9);
       priorityQueue.add(5);
       priorityQueue.add(4);

       System.out.print("最大堆:");
       priorityQueue.print();

       System.out.println();
       priorityQueue.pop();

       System.out.print("pop后最大堆:");
       priorityQueue.print();

   }

}

参见

  • 小灰学算法