数据结构-表、树、图

一、表

1.1、散列表

也叫哈希表,把数据分散在列表中,依赖于数组下标访问的特性,数组的一种拓展。

散列思想:

即映射思想,用键值对来保存信息,键(key)和值(value)间的映射法则叫散列函数(Hash函数),通过散列函数得到的值叫散列值(Hash值)。

散列函数:

散列函数根据key生成一个对应的散列值(数组下标),将key的信息保存到该数组下标对应的内存空间。

构造散列函数三个基本要求

1.通过散列函数得到的散列值是一个非负整数,因为散列值是数组下标。

2.如果key1 == key2,则hash(key1) == hash(key2),保证同样的key只对应一个散列值。

3.如果key1!= key2,则hash(key1)!= hash(key2),保证同样的key只对应一个散列值。

散列冲突:

不同的key经过散列函数散列后,会出现相同的散列值。

同义词:

这些发生冲突的不同关键字称为同义词。

装填因子:

散列表的装填因子一般记为α,定义为一个表的装满程度。
装填因子

散列冲突(Hash冲突)的解决

不同的key经过散列函数散列后,会出现相同的散列值。

1.开放寻址法

出现一个重复散列值,寻找空闲位置插入。

1.1线性探测法

从当前已占用位置开始,寻找下一个空闲位置( hash(key)+0,hash(key)+1,hash(key)+2......),直到找到为止。

1.2二次探测

探测的步长就变成了线性探测的"二次方"(hash(key)+0,hash(key)+12,hash(key)+22......)。

1.3双重散列

用一组散列函数(hash1(key),hash2(key),hash3(key)......),第一个散列函数计算的存储位置被占用,用第二个散列函数,依次类推,直到找到空闲的存储位置。

2.链表法

本质是数组加链表,数组保存的是每个槽的头链表。每个槽(slot)"对应一条链表,散列值相同元素放到同槽位对应的链表中。

二、树

2.1、二叉树

非线性数据结构,由多个节点组成的有层次关系集合,像一颗倒挂的树。

节点的度:一个结点含有子树的个数。

树的度:节点的度的最大值。

叶子节点(终端节点):度为0的节点。

父节点(双亲节点):一个节点的前驱节点称为此节点的父节点。

子节点(孩子节点):一个节点的后继节点称为此节点的子节点。

根节点:树中没有父节点的节点。

节点层次:从根节点开始往下,根节点为1层,以此类推。

树的高度:节点的最大层数。

树的深度:根节点到指定节点的最长路径的节点数。

2.1.1、二叉树种类

2.1.1.1、满二叉树

二叉树的所有叶子节点都在最后一层,且结点总数=2^n-1,n为层数。

2.1.1.2、完全二叉树

所有叶子节点都在最后一层或者倒数第二层,且最后一层的叶子节点在左边连接,倒数第二层的叶子节点在右边连接。

2.1.1.3、二叉搜索树(二叉排序树、二叉查找树)

左子树上所有节点的值均小于它的根节点的值,右子树上所有节点的值均大于它的根节点的值。

java 复制代码
import java.util.LinkedList;
import java.util.Queue;
 
/**
 * 二分搜索树
 */
public class MyBST<E extends Comparable<E>> {
 
    private Node root;
	
    private int size;
 
    public MyBST(){
        this.root = null;
        this.size = 0;
    }
 
    // 向二分搜索树中添加新的元素e
    public void add(E e){
        root = add(root,e);
    }
 
    // 向以node为根的二分搜索树中插入元素e,递归算法。 返回插入新节点后二分搜索树的根
    private Node add(Node node, E e){
        if(node == null){
            size = size +1;
            return new Node(e);
        }
        if(e.compareTo(node.e) < 0){
          node.left = add(node.left,e);
        }else if(e.compareTo(node.e) > 0){
          node.right =  add(node.right,e);
        }
        return node;
    }
 
    // 看二分搜索树中是否包含元素e
    public boolean contains(E e){
        return contains(root, e);
    }
 
    // 看以node为根的二分搜索树中是否包含元素e,递归算法
    public boolean contains(Node node,E e){
        if(node==null){
            return false;
        }
        if(e.compareTo(node.e) < 0){
            return contains(node.left,e);
        }else if(e.compareTo(node.e) > 0){
            return contains(node.right,e);
        }else {
            return true;
        }
    }
 
    // 二分搜索树的前序遍历
    public void preOrder(){
        if(root==null){
            System.out.println("空树");
        }else {
            preOrder(root);
        }
    }
    // 前序遍历以 node为根的二分搜索树, 递归算法
    public void preOrder(Node node){
        if(node == null){
            return;
        }
        System.out.println(node.e);
        preOrder(node.left);
        preOrder(node.right);
    }
 
    // 二分搜索树的中序遍历
    public void inOrder(){
        inOrder(root);
    }
    // 中序遍历以node为根的二分搜索树, 递归算法
    private void inOrder(Node node){
        if(node == null){
            return;
        }
        inOrder(node.left);
        System.out.println(node.e);
        inOrder(node.right);
    }
	
    // 二分搜索树的后序遍历
    public void postOrder(){
        postOrder(root);
    }
    // 后序遍历以node为根的二分搜索树, 递归算法
    private void postOrder(Node node){
        if(node == null){
            return;
        }
        postOrder(node.left);
        postOrder(node.right);
        System.out.println(node.e);
    }
	
    // 二分搜索树的层序遍历
    public void levelOrder(){
        if(root == null){
            return;
        }
        Queue<Node> que = new LinkedList<>();
        que.add(root);
        while(!que.isEmpty()){
            Node cur = que.remove();
            System.out.println(cur.e);
            if(cur.left != null){
                que.add(cur.left);
            }
            if(cur.right != null){
                que.add(cur.right);
            }
        }
    }
	
    // 从二分搜索树中删除元素为e的节点
    public void remove(E e){
        root = remove(root,e);
    }
	
    // 删除掉以node为根的二分搜索树中值为e的节点, 递归算法
    // 返回删除节点后新的二分搜索树的根
    public Node remove(Node node,E e){
        if( node == null ){
            return null;
        }
        if(e.compareTo(node.e) < 0){
            node.left = remove(node.left,e);
            return node;
        }else if(e.compareTo(node.e)>0){
            node.right = remove(node.right,e);
            return node;
        }else {
            if(node.left == null){
                Node rightNode = node.right;
                node.right = null;
                size = size-1;
                return rightNode;
            }
            if(node.right == null){
                Node leftNode = node.left;
                node.left = null;
                size = size-1;
                return leftNode;
            }
            // 待删除节点左右子树均不为空的情况
            // 找到比待删除节点大的最小节点,即待删除节点右子树的最小节点,用这个节点顶替待删除节点的位置
            Node successor = minimum(node.right);
            successor.right = removeMin(node.right);
            successor.left = node.left;
            node.left = node.right = null;
            return successor;
        }
    }
 
    // 返回以node为根的二分搜索树的最小值所在的节点
    private Node minimum(Node node){
        if(node.left == null){
            return node;
        }
        return minimum(node.left);
    }
 
    // 删除掉以node为根的二分搜索树中的最小节点,返回删除节点后新的二分搜索树的根
    private Node removeMin(Node node){
        if(node.left == null){
            Node rightNode = node.right;
            node.right = null;
            size --;
            return rightNode;
        }
        node.left = removeMin(node.left);
        return node;
    }
 
    private class Node{
        public E e;
        public Node left;
        public Node right;
        public Node(E e){
            this.e = e;
            this.left = null;
            this.right = null;
        }
    }
}
2.1.1.4、平衡二叉树

每个结点的左子树的高度减去右子树的高度的绝对值不超过1

左旋:

根节点的右侧往左拉,原右子节点成新的父节点,原右节子点的左子节点给降级的根节点当右子节点。

右旋:

根节点的左侧往右拉,原左子节点成新的父节点,原左子节点的右子节点给降级的根节点当左子节点。

java 复制代码
import java.util.ArrayList;
 
/**
 * 平衡二叉树
 */
public class MyAVLTree<K extends Comparable<K>, V> {
 
    private Node root;
    private int size;

    public MyAVLTree(){
        root = null;
        size = 0;
    }
 
    // 判断该二叉树是否是一棵二分搜索树。
    public boolean isBST(){
        ArrayList<K> keys = new ArrayList<>();
        inOrder(root, keys);
        for(int i = 1 ; i < keys.size() ; i ++){
            if(keys.get(i - 1).compareTo(keys.get(i)) > 0){
                return false;
            }
        }
        return true;
    }
 
    //中置遍历,顺序取出各个节点。
    private void inOrder(Node node, ArrayList<K> keys){
        if(node == null){
            return;
        }
        inOrder(node.left, keys);
        keys.add(node.key);
        inOrder(node.right, keys);
    }
 
    // 判断该二叉树是否是一棵平衡二叉树。
    public boolean isBalanced(){
        return isBalanced(root);
    }
 
    // 判断以Node为根的二叉树是否是一棵平衡二叉树,递归算法。
    private boolean isBalanced(Node node){
        if(node == null){
            return true;
        }
        int balanceFactor = getBalanceFactor(node);
        if(Math.abs(balanceFactor) > 1){
            return false;
        }
        return isBalanced(node.left) && isBalanced(node.right);
    }
 
    // 获得节点node的平衡因子
    private int getBalanceFactor(Node node){
        if(node == null){
            return 0;
        }
        return node.left.height- node.right.height;
    }
 
    // 对节点y进行向右旋转操作,返回旋转后新的根节点x。
    //        y                              x
    //       / \                           /   \
    //      x   T4     向右旋转 (y)        z     y
    //     / \       - - - - - - - ->    / \   / \
    //    z   T3                       T1  T2 T3 T4
    //   / \
    // T1   T2
    private Node rightRotate(Node y) {
        Node x = y.left;
        Node T3 = x.right;
        // 向右旋转过程
        x.right = y;
        y.left = T3;
        // 更新height
        y.height = Math.max(y.left.height,y.right.height) + 1;
        x.height = Math.max(x.left.height,x.right.height) + 1;
        return x;
    }
 
    // 对节点y进行向左旋转操作,返回旋转后新的根节点x。
    //    y                             x
    //  /  \                          /   \
    // T1   x      向左旋转 (y)       y     z
    //     / \   - - - - - - - ->   / \   / \
    //   T2  z                     T1 T2 T3 T4
    //      / \
    //     T3 T4
    private Node leftRotate(Node y) {
        Node x = y.right;
        Node T2 = x.left;
        // 向左旋转过程
        x.left = y;
        y.right = T2;
        // 更新height
        y.height = Math.max(y.left.height,y.right.height) + 1;
        x.height = Math.max(x.left.height,x.right.height) + 1;
        return x;
    }
 
    // 向二分搜索树中添加新的元素(key, value)。
    public void add(K key, V value){
        root = add(root, key, value);
    }
 
    // 向以node为根的二分搜索树中插入元素(key, value), 递归算法。
    // 返回插入新节点后二分搜索树的根。
    private Node add(Node node, K key, V value){
        if(node == null){
            size = size+1;
            return new Node(key, value);
        }
        if(key.compareTo(node.key) < 0){
            node.left = add(node.left, key, value);
        } else if(key.compareTo(node.key) > 0){
            node.right = add(node.right, key, value);
        } else{  // key.compareTo(node.key) == 0
            node.value = value;
        }
        // 更新height
        node.height = 1 + Math.max(node.left.height,node.right.height);
        // 计算平衡因子
        int balanceFactor = getBalanceFactor(node);
        // 平衡维护
        // LL
        if (balanceFactor > 1 && getBalanceFactor(node.left) >= 0){
            return rightRotate(node);
        }
        // RR
        if (balanceFactor < -1 && getBalanceFactor(node.right) <= 0){
            return leftRotate(node);
        }
        // LR
        if (balanceFactor > 1 && getBalanceFactor(node.left) < 0) {
            node.left = leftRotate(node.left);
            return rightRotate(node);
        }
        // RL
        if (balanceFactor < -1 && getBalanceFactor(node.right) > 0) {
            node.right = rightRotate(node.right);
            return leftRotate(node);
        }
        return node;
    }
 
    // 返回以node为根节点的二分搜索树中,key所在的节点。
    private Node getNode(Node node, K key){
        if(node == null){
            return null;
        }
        if(key.equals(node.key)){
            return node;
        }else if(key.compareTo(node.key) < 0){
            return getNode(node.left, key);
        }else { // if(key.compareTo(node.key) > 0)
            return getNode(node.right, key);
        }
    }
 
    public boolean contains(K key){
        return getNode(root, key) != null;
    }
 
    public V get(K key){
        Node node = getNode(root, key);
        return node == null ? null : node.value;
    }
 
    public void set(K key, V newValue){
        Node node = getNode(root, key);
        if(node == null){
            throw new IllegalArgumentException(key + " 不存在!");
        }
        node.value = newValue;
    }
 
    // 返回以node为根的二分搜索树的最小值所在的节点
    private Node minimum(Node node){
        if(node.left == null){
            return node;
        }
        return minimum(node.left);
    }
 
    // 从二分搜索树中删除键为key的节点
    public V remove(K key){
        Node node = getNode(root, key);
        if(node != null){
            root = remove(root, key);
            return node.value;
        }
        return null;
    }
 
    private Node remove(Node node, K key){
        if( node == null ){
            return null;
        }
        Node retNode;
        if( key.compareTo(node.key) < 0 ){
            node.left = remove(node.left , key);
            retNode = node;
        }else if(key.compareTo(node.key) > 0 ){
            node.right = remove(node.right, key);
            retNode = node;
        }else{ // key.compareTo(node.key) == 0
            // 待删除节点左子树为空的情况
            if(node.left == null){
                Node rightNode = node.right;
                node.right = null;
                size = size-1;
                retNode = rightNode;
            }
            // 待删除节点右子树为空的情况
            else if(node.right == null){
                Node leftNode = node.left;
                node.left = null;
                size = size-1;
                retNode = leftNode;
            }else{ // 待删除节点左右子树均不为空的情况.
                // 找到比待删除节点大的最小节点, 即待删除节点右子树的最小节点.
                // 用这个节点顶替待删除节点的位置.
                Node successor = minimum(node.right);
                successor.right = remove(node.right, successor.key);
                successor.left = node.left;
                node.left = node.right = null;
                retNode = successor;
            }
        }
        if(retNode == null){
            return null;
        }
        // 更新height
        retNode.height = 1 + Math.max(retNode.left.height,retNode.right.height);
        // 计算平衡因子
        int balanceFactor = getBalanceFactor(retNode);
        // 平衡维护
        // LL
        if (balanceFactor > 1 && getBalanceFactor(retNode.left) >= 0){
            return rightRotate(retNode);
        }
        // RR
        if (balanceFactor < -1 && getBalanceFactor(retNode.right) <= 0){
            return leftRotate(retNode);
        }
        // LR
        if (balanceFactor > 1 && getBalanceFactor(retNode.left) < 0) {
            retNode.left = leftRotate(retNode.left);
            return rightRotate(retNode);
        }
        // RL
        if (balanceFactor < -1 && getBalanceFactor(retNode.right) > 0) {
            retNode.right = rightRotate(retNode.right);
            return leftRotate(retNode);
        }
        return retNode;
    }
 
    private class Node{
        public K key;
        public V value;
        public Node left;
        public Node right;
        public int height;
        public Node(K key, V value){
            this.key = key;
            this.value = value;
            left = null;
            right = null;
            height = 1;
        }
    }
}
2.1.1.5、平衡二叉树---AVL树

带了平衡功能的二叉查找树(二叉排序树,二叉搜索树),每个结点的左右子树的高度之差的绝对值(平衡因子)最多为1。

2.1.1.6、平衡二叉树---红黑树

每个结点非红即黑,根节点是黑色的,每个红色结点的两个子结点都是黑色(无连续的红结点)。

从任一结点到其每个叶子的所有路径都包含相同数目的黑结点。

保证最长路径不超过最短路径的2倍,降低了插入和旋转的次数,增删的结构中性能比AVL树更优。

2.1.2、二叉树的遍历

2.1.2.1、前序遍历(先根遍历、根左右)

先输出父节点,再遍历左子树和右子树。

2.1.2.2、中序遍历(中根遍历、左根右)

先遍历左子树,再输出父节点,再遍历右子树。

2.1.2.3、后序遍历(后根遍历、左右根)

先遍历左子树,再遍历右子树,最后输出父节点。

2.1.2.4、层序遍历

按二叉树的层次从上至下从左至右进行遍历

2.1.2.5、S形层序遍历

奇数层从左往右打印,偶数层从右向左打印。

2.2、二叉树---红黑树

三、图

3.1、图

图(Graph)一种多对多的关系,由节点(或顶点)和连接这些节点的边(或弧)组成。

可用来表示各种实际应用场景中的关系和连接,如网络路由、社交网络分析、交通运输、电路设计等。

图也是许多算法的基础,如最短路径算法、最小生成树算法、拓扑排序算法等。

图的基本概念

顶点(Vertex): 图中的点被称为顶点,每个顶点可以代表一个实体,如人、物体、事件等。
边(Edge): 连接两个顶点的线段称为边。边可以表示这两个顶点之间的关系或连接。
邻接点(Adjacent Vertices): 与一条边相连的两个顶点称为该边的邻接点。
路径(Path): 从一个顶点到另一个顶点的一系列边和顶点称为路径。

环(Cycle):一条路径,其中某个顶点是通过边连接回到路径中的先前顶点,称为环。
无向图(Undirected Graph): 边没有方向,即两个顶点可以通过一条边相互连接。
有向图(Directed Graph): 边有方向,即一个顶点可以通过一条边指向另一个顶点。
简单图(Simple Graph): 不包含重复边和重复顶点的图称为简单图。
加权图(Weighted Graph): 边带有权重的图称为加权图。权重可以表示距离、成本或其他测量值。
稀疏图(Sparse Graph): 边的数量相对较少的图称为稀疏图。
**稠密图(Dense Graph):**边的数量相对较多的图称为稠密图。

3.2、位图(bitmap)

用一系列位来表示数据,每个位只有两个状态(0或1),在存储大量数据时高效且节省空间。

java 复制代码
import java.util.HashSet;

public class Test01 {

	public static class BitMap {
	
		//定义long数组,一个元素可以存放64个数,因为long类型是8字节 64位
		private long[] bits;
		
		//定义数组长度,需要先给需要存储的数集的最大值 (max+64/64) 表示需要多少个,如 max是63 那个得 1 只需一个元素就可以
		//如果是64 得 2,就需要2个元素 因为一个Long64位 存放0-63的数值, 接着是64-127
		public BitMap(int max) { bits = new long[(max + 64) >> 6];}
		
		/**
		* 保存数值
		* 1.num>>6 表示先将目标数值除以64 得到该数值是位于bits数组的第几个,比如63 / 64 = 0 在bit[0] 64/64=1 bit[1]..
		* 2.num & 63 表示num % 64 , 即看在该元素的第几位, 2的6次方=64 第7位1000000 所以可以知道求模后肯定余数是1-6位的数,
	    *  那么与运算 111111 即 63 就能表示1-6的值,也就是余数,也就得到是位于该元素中的第几位了 比如余数0 那么就是第一位 ,
	    *  1 就是第二位...  确定了这个余数 即第几位,那么我们就用 1l << 余数(1必须要为long型 1L 取值长度才不会越界,int是32位,
	    *  所以右移63肯定位数不够)  将1右移余数位,比如余数1 那么就1L右移一位,表示在这个位置标1了。
	    *  3.前面得到了对应在第几个元素中的第几位,最后就是需要在这个元素的这个位置赋值1. 那么就是可以将元素位置bits[num >>6] =
	    *   bits[num >>6] | (1L << (num & 64)  即或运算 前面得到的具体位置,有1 则表示1 所以就给元素的对于位置赋值1了
		*/
		
		public void add(int num) {
			bits[num >> 6] |= (1L << (num & 63));
		}
 
		/**
		 * 删除数值, 就是将对应的元素中的第几位 将其1改成0
		 * 1.bits[num >> 6] 元素所在数组的元素, 1L << (num & 63) 64位元素中的第几位 表示存放着num
		 * 2.对1L << (num & 63) 取反, 就得到一个 1111...01111 即将存放位赋值0 其他为1
		 * 3.将取反后的数 与bits[num >> 6] 所在元素就行 与运算, 此时其他位都位1 与完元素不变,而num所在位是0 与完则为0,则
		 * 表示将数组删除
		 */
		public void delete(int num) {
			bits[num >> 6] &= ~(1L << (num & 63));
		}
 
		/**
		 * 判断数值是否包含,那么就是判断 bits[num >> 6] 所在元素的(1L << (num & 63)) 所在位 进行与运算,(1L << (num & 63))
		 * 所在位为1 其他位都为0 所以与运算后 如果为一 表示bits[num >> 6] 的所在位也是1 那么就返回true
		 */
		public boolean contains(int num) {
			return (bits[num >> 6] & (1L << (num & 63))) != 0;
		}
	}
 
	public static void main(String[] args) {
		int max = 10000;
		BitMap bitMap = new BitMap(max);
		HashSet<Integer> set = new HashSet<>();
		int testTime = 10000000;
		for (int i = 0; i < testTime; i++) {
			int num = (int) (Math.random() * (max + 1));
			double decide = Math.random();
			if (decide < 0.333) {
				bitMap.add(num);
				set.add(num);
			} else if (decide < 0.666) {
				bitMap.delete(num);
				set.remove(num);
			} else {
				if (bitMap.contains(num) != set.contains(num)) {
					System.out.println("Oops!");
					break;
				}
			}
		}
		for (int num = 0; num <= max; num++) {
			if (bitMap.contains(num) != set.contains(num)) {
				System.out.println("Oops!");
			}
		}
	}
}

四、堆(Heap)

堆总是一棵完全二叉树,某个节点的值总是不大于或不小于其父节点的值。

相关推荐
肥猪猪爸30 分钟前
使用卡尔曼滤波器估计pybullet中的机器人位置
数据结构·人工智能·python·算法·机器人·卡尔曼滤波·pybullet
linux_carlos30 分钟前
环形缓冲区
数据结构
readmancynn42 分钟前
二分基本实现
数据结构·算法
Bucai_不才1 小时前
【数据结构】树——链式存储二叉树的基础
数据结构·二叉树
盼海1 小时前
排序算法(四)--快速排序
数据结构·算法·排序算法
一直学习永不止步1 小时前
LeetCode题练习与总结:最长回文串--409
java·数据结构·算法·leetcode·字符串·贪心·哈希表
珹洺2 小时前
C语言数据结构——详细讲解 双链表
c语言·开发语言·网络·数据结构·c++·算法·leetcode
几窗花鸢2 小时前
力扣面试经典 150(下)
数据结构·c++·算法·leetcode
.Cnn3 小时前
用邻接矩阵实现图的深度优先遍历
c语言·数据结构·算法·深度优先·图论
2401_858286113 小时前
101.【C语言】数据结构之二叉树的堆实现(顺序结构) 下
c语言·开发语言·数据结构·算法·