常用的数据结构

1.数组(Array)
数组可以说是最基本的数据结构。它是将相同类型的一些数据有序的集合在一起。
一个数组可以包含多个同类型的数据元素。
可以按照数据类型分为整型数组、字符型数组、浮点型数组、对象数组等。
可以按照维度分为一维数组、二维数组、多维数组等。

  • 2.队列(Queue)
    队列也是一种特殊的线性表。只允许在一端(队尾)进行插入,在另一端(队头)进行删除。
    线性表就是各个数据元素之间具有线性关系。就像一条线一样。有头有尾,中间一个结点连着一个结点,不可间断。
    队列就像水管一样,水只能从一端流入,从另一端流出。
  • 3.链表(Linked List)
    链表的元素是按照链式存储的。也是线性结构中的一种。线性结构包括顺序结构和链表结构两种。
    这种存储在物理上并非连续的,而是每个元素包括两个部分,一部分是自己的数据,另一部分是引用域,指向下一个元素所在的地址。
    链表的逻辑关系是通过引用域串联起来的。
    线性结构中的顺序表结构在进行插入或者删除的时候,需要移动大量的数据。而链表结构最多只需要修改关联的前后两个结点的引用域即可。
    链表包含单向链表和双向链表,单向链的引用域只指向下一个元素的地址,双向链表的引用域既包含指向下一个的地址,也有指向上一个的地址。
  • 4.树(Tree)
    典型的非线性结构,有且仅有一个根结点,根结点会有一个或多个子结点。
    然后子结点也可能会有一个或多个子结点。但是,每个子结点都只会有一个父结点。
    终结点没有子结点。
    每个节点后面的所有节点组成的树称为这个结点的子树。
    二叉树是一种比较特殊的树,每一个结点最多都只有两个子结点。称为左结点和右结点。
    二叉树又包括完全二叉树(有子结点的结点都有两个子结点),和非完全二叉树(有些结点只有一个子结点)。
  • 5.堆(Heap)
    堆是一种特殊的树形结构,我们一般讨论的堆都是二叉堆。
    堆的特点是根结点的值是所有结点中最小的或者最大的。并且根结点的子树也是一个堆结构。
  • 6.栈(Stack)
    栈是一种特殊的线性表,只能在表的一端(栈顶)进行插入(入栈)和删除(出栈)操作。
    第一个插入的数据会被后面插入的不断的压入栈底。要想操作栈底的数据,只能先将其推到栈顶。
  • 7.图(Graph)
    图是另外一种非线性结构,例如通信网络、交通网络、人机关系网络,都可以归为图结构
    图可以分为无向图和有向图,
    无向图没有方向,从A到B和从B到A一样,
    有向图具有方向,就像单行道,只能从A到B,不可以逆向从B到A
    连接图的某个定点到其他所有定点的边的数量称为该顶点的度。
  • 8.散列表(Hash)
    源自散列函数(Hash function),其思想是如果在结构中存在关键字和T相等的记录,那么必定在F(T)的存储位置可以找到该记录,这样就可以不用进行比较而直接取得所查记录

2.链表结构

 

 

    /**
     * 定义数据结构中的元素
     */
    static class DataElement {
        String key;
        String other_messages;

        public DataElement(String key, String other_messages) {
            this.key = key;
            this.other_messages = other_messages;
        }
    }

    /**
     * 定义链表结构
     * 链表包含单向链表和双向链表,单向链的引用域只指向下一个元素的地址,双向链表的引用域既有指向下一个的,也有指向上一个的
     */
    static class DataLinkedList {
        DataElement dataElement;
        DataLinkedList nextNode;
        //我们这里只做查找算法的示例,省去添加节点,删除节点等其他方法,只做查找

        /**
         * 在链表的尾部追加数据
         *
         * @param head 链表的表头
         * @param data 要添加的数据
         * @return 添加后的链表元素(数据+引用域)
         */
        static DataLinkedList addData(DataLinkedList head, DataElement data) {
            DataLinkedList node = new DataLinkedList();//新追加的结点是在结尾追加
            node.dataElement = data;//保存数据
            node.nextNode = null;//表尾引用域为null,

            DataLinkedList element = head;
            if (element == null) {//链表的表头为null,是个空链表,直接将head指向新节点
                head = node;
                return head;
            }
            while (element.nextNode != null) {//找到链表的尾部,如果已知链表尾部,则可以直接用,不用再从表头查找
                element = element.nextNode;
            }
            element.nextNode = node;//将链表的尾部指向新追加的结点
            return node;
        }

        /**
         * 在表头追加数据
         *
         * @param head 表头
         * @param data 要添加的数据
         * @return 添加后的链表元素(数据+引用域)
         */
        static DataLinkedList addHeadData(DataLinkedList head, DataElement data) {
            DataLinkedList node = new DataLinkedList();//新追加的结点是在链表头部追加
            node.dataElement = data;//保存数据
            node.nextNode = head;//指向下一个元素是之前的表头
            head = node;//表头变成新增的这个元素
            return head;
        }

        /**
         * 在指定的key后面追加数据
         *
         * @param head 表头
         * @param key  元素数据的key
         * @param data 要插入的新数据
         * @return 插入的新元素(数据+引用)
         */
        static DataLinkedList insertData(DataLinkedList head, String key, DataElement data) {
            DataLinkedList keyData = null;
            //查找key所在的位置
            DataLinkedList temp = head;
            while (temp != null) {
                DataElement date = temp.dataElement;
                if (date.key.equals(key)) {//节点的关键字与传入的关键字相同
                    keyData = temp;//找到key的位置
                    break;
                }
                temp = temp.nextNode;//处理下一节点
            }
            if (keyData != null) {
                DataLinkedList node = new DataLinkedList();//新追加的结点是在链表头部追加
                node.dataElement = data;//保存数据
                node.nextNode = keyData.nextNode;//新元素指向key所指向的下一个元素
                //如果是双向链表,keyData.nextNode的元素的previousNode也要指向新添加的元素node,
                //而新添加的node的previousNode也要指向keyData
                keyData.nextNode = node;//key指向新添加的元素
                return node;
            }
            return null;//未找到指定key所在的位置
        }

        /**
         * 链表结构中的查找算法
         * 一般来说可以通过关键字查询,从表头依次查找
         *
         * @param head 链表表头
         * @param key  查找的关键字
         */
        static DataLinkedList findData(DataLinkedList head, String key) {
            DataLinkedList temp = head;//保存表头指针
            while (temp != null) {//节点有效,进行查找
                DataElement date = temp.dataElement;
                if (date != null && date.key != null) {
                    if (date.key.equals(key)) {//节点的关键字与传入的关键字相同
                        return temp;//返回该节点指针
                    }
                }
                temp = temp.nextNode;//处理下一节点
            }
            return null;//未找到,返回空指针
        }

        /**
         * 删除key对应的元素
         *
         * @param head 表头
         * @param key  数据的关键字
         * @return 删除成功还是失败
         */
        static boolean deleteNode(DataLinkedList head, String key) {
            DataLinkedList previousData = null;
            DataLinkedList temp = head;
            while (temp != null) {//节点有效,进行查找
                DataElement date = temp.dataElement;
                if (date != null && date.key != null) {
                    if (date.key.equals(key)) {//节点的关键字与传入的关键字相同
                        if (previousData != null) {
                            previousData.nextNode = temp.nextNode;//将上一个节点的nextNode指向当前节点的下一个结点
                            temp = null;//删除当前节点释放内存
                            return true;
                        }
                    }
                }
                previousData = temp;
                temp = temp.nextNode;//处理下一节点
            }
            return false;//没有找到key对应的结点
        }

        /**
         * 获取链表长度
         *
         * @param head 表头
         * @return 链表长度
         */
        static int size(DataLinkedList head) {
            DataLinkedList temp = head;
            int size = 0;
            while (temp != null) {
                size++;//当前结点非空,数量+1
                temp = temp.nextNode;//处理下一节点
            }
            return size;
        }

        /**
         * 输出所有结点数据
         *
         * @param head 表头
         */
        static void showAllNode(DataLinkedList head) {
            DataLinkedList temp = head;
            while (temp != null) {
                //输出当前结点的数据
                DataElement date = temp.dataElement;
                if (date != null && date.key != null) {
                    System.out.printf("data:{key:%s,otherMessage:%s}%n",
                            date.key, date.other_messages);
                }

                temp = temp.nextNode;//处理下一节点
            }
        }

    }

调用:

        DataLinkedList head = new DataLinkedList();
        head.dataElement = new DataElement("key1", "otherMessage1");
        head.nextNode = null;
        System.out.printf("开始在头部追加结点keyHead.%n");
        head = DataLinkedList.addHeadData(head, new DataElement("keyHead", "otherMessageHead"));
        if (head != null) {
            System.out.printf("链表头部追加结点成功!%n");
        }
        System.out.printf("开始在尾部追加结点key2.%n");
        DataLinkedList node = DataLinkedList.addData(head, new DataElement("key2", "otherMessage2"));
        if (node != null) {
            System.out.printf("链表尾部追加结点成功!%n");
        }
        System.out.printf("开始在key1后插入结点key3.%n");
        node = DataLinkedList.insertData(head, "key1", new DataElement("key3", "otherMessage3"));
        if (node != null) {
            System.out.printf("在key1后插入结点成功!%n");
        }
        System.out.printf("开始查找key2所在的结点,%n");
        node = DataLinkedList.findData(head, "key2");
        if (node != null) {
            System.out.printf("key2所在的结点:data:{key:%s,otherMessage:%s},nextNodeAddress:{%s}%n",
                    node.dataElement.key, node.dataElement.other_messages, node.nextNode);
        }
        int size = DataLinkedList.size(head);
        System.out.printf("链表中总长度为:%d%n", size);
        System.out.printf("链表中的所有元素:%n");
        DataLinkedList.showAllNode(head);
        boolean delete = DataLinkedList.deleteNode(head, "key2");
        System.out.printf("链表中删除key2元素的结果:%b%n", delete);

输出:

开始在头部追加结点keyHead.
链表头部追加结点成功开始在尾部追加结点key2.
链表尾部追加结点成功开始在key1后插入结点key3.
在key1后插入结点成功开始查找key2所在的结点
key2所在的结点:data:{key:key2,otherMessage:otherMessage2},nextNodeAddress:{null} 
链表中总长度为4
链表中的所有元素:
data:{key:keyHead,otherMessage:otherMessageHead}
data:{key:key1,otherMessage:otherMessage1}
data:{key:key3,otherMessage:otherMessage3}
data:{key:key2,otherMessage:otherMessage2}
链表中删除key2元素的结果true

3.队列结构

 

 

由于队列结构比较简单,就不做详细的示例,代码可以参考下面的栈结构,将栈顶作为队头,并增加队尾标记,以及对队头、队尾的相关操作即可。

4.树结构

 

 

 

 

 

 

 

 

    /**
     * 定义二叉树结构
     */
    static class TreeDate {
        String data;
        TreeDate leftData;
        TreeDate rightData;

        /**
         * 初始化二叉树
         *
         * @param rootData 根结点的数据
         * @return TreeDate二叉树的树根
         */
        static TreeDate initTree(String rootData) {
            TreeDate treeRoot = new TreeDate();
            treeRoot.data = rootData;
            treeRoot.leftData = null;
            treeRoot.rightData = null;
            return treeRoot;
        }

        /**
         * 添加树结点
         *
         * @param treeRoot    树的跟结点
         * @param parentData  要添加的结点位于哪个父结点下
         * @param leftOrRight 作为父结点的左子树(1)还是右子树(2)
         * @param nodeData    要添加的结点数据
         */
        static void addNode(TreeDate treeRoot, String parentData, int leftOrRight, String nodeData) {
            TreeDate parentNode = findData(treeRoot, parentData);//根据key找到父结点
            if (parentNode != null) {
                TreeDate node = new TreeDate();
                node.data = nodeData;
                node.leftData = null;
                node.rightData = null;
                switch (leftOrRight) {
                    case 1:
                        if (parentNode.leftData != null) {
                            System.out.printf("父节点的左子树不为空!");
                        } else {
                            parentNode.leftData = node;
                        }
                        break;
                    case 2:
                        if (parentNode.rightData != null) {
                            System.out.printf("父节点的右子树不为空!");
                        } else {
                            parentNode.rightData = node;
                        }
                        break;
                }
            } else {
                System.out.printf("父节点[%s]未找到!", parentData);
            }

        }

        /**
         * 二叉树查找算法
         * 遍历二叉树中的每一个结点,逐个比较数据。
         *
         * @param treeNode 树结点,首次调用传入树的根结点
         * @param data     要查找的结点
         * @return TreeDate 查找结果
         */
        static TreeDate findData(TreeDate treeNode, String data) {
            if (treeNode == null) {
                return null;
            } else {
                if (treeNode.data.equals(data)) {
                    return treeNode;
                }
                if (findData(treeNode.leftData, data) != null) {//递归查找左结点
                    return findData(treeNode.leftData, data);
                }
                if (findData(treeNode.rightData, data) != null) {//递归查找右结点
                    return findData(treeNode.rightData, data);
                }
            }
            return null;
        }

        /**
         * 获取左子树
         *
         * @param treeNode 树的一个结点
         * @return 左子树
         */
        static TreeDate leftNode(TreeDate treeNode) {
            if (treeNode != null) {
                return treeNode.leftData;
            }
            return null;
        }

        /**
         * 获取右子树
         *
         * @param treeNode 树的一个结点
         * @return 右子树
         */
        static TreeDate rightNode(TreeDate treeNode) {
            if (treeNode != null) {
                return treeNode.rightData;
            }
            return null;
        }

        /**
         * 判断空树
         *
         * @param treeRoot 根结点
         * @return true空树,false不是空树
         */
        static boolean isEmpty(TreeDate treeRoot) {
            return treeRoot == null;
        }

        /**
         * 计算二叉树的深度,计算二叉树的最大层数
         *
         * @param treeNode 从根结点开始
         * @return 二叉树的最大层数int
         */
        static int depth(TreeDate treeNode) {
            int depthLeft = 0, depthRight = 0;
            if (treeNode == null) {
                return 0;
            }
            if (treeNode.leftData != null) {
                depthLeft = depth(treeNode.leftData);
            }
            if (treeNode.rightData != null) {
                depthRight = depth(treeNode.rightData);
            }

            if (depthLeft > depthRight) {
                return depthLeft + 1;
            }
            return depthRight + 1;
        }

        /**
         * 清空二叉树
         * 对于JVM,这个clearTree,是unnecessary的,不必要的
         *
         * @param treeNode 根结点
         */
        static void clearTree(TreeDate treeNode) {
            if (treeNode != null) {
                clearTree(treeNode.leftData);
                clearTree(treeNode.rightData);
                treeNode = null;//释放内存
            }
        }

        /**
         * 显示当前结点的数据
         *
         * @param treeNode 当前结点
         */
        static void nodeData(TreeDate treeNode) {
            System.out.printf(" %s->", treeNode.data);
        }

        /**
         * 遍历二叉树(按层遍历)
         *
         * @param treeRoot 根结点
         */
        static void traverseTreeAccordingToLevel(TreeDate treeRoot) {
            int head = 0, tail = 0;
            final int MAX_LENGTH = 100;//树的最大结点数
            TreeDate p;
            TreeDate[] q = new TreeDate[MAX_LENGTH];//定义一个顺序栈
            if (treeRoot != null) {//如果队首引用不为空
                tail = (tail + 1) % MAX_LENGTH;//计算循环队列队尾序号
                q[tail] = treeRoot;//将二叉树根引用进队
            }
            while (head != tail) {//队列不为空,进行循环
                head = (head + 1) % MAX_LENGTH;//计算循环队列的队首序号
                p = q[head];//获取队首元素
                nodeData(p);//处理队首元素
                if (p.leftData != null) {//存在左子树
                    tail = (tail + 1) % MAX_LENGTH;//计算循环队列的队尾序号
                    q[tail] = p.leftData;//将左子树引用进队
                }
                if (p.rightData != null) {
                    tail = (tail + 1) % MAX_LENGTH;
                    q[tail] = p.rightData;
                }
            }
        }

        /**
         * 遍历二叉树(先序遍历算法)
         * 先按照中序遍历左子树,再访问根结点,最后按中序遍历右子树
         *
         * @param treeRoot 根结点
         */
        static void traverseTreeAccordingToPerorder(TreeDate treeRoot) {
            if (treeRoot != null) {
                nodeData(treeRoot);//显示结点数据
                traverseTreeAccordingToPerorder(treeRoot.leftData);
                traverseTreeAccordingToPerorder(treeRoot.rightData);
            }
        }

        /**
         * 遍历二叉树(中序遍历算法)
         * 先访问根结点,再按照先序遍历左子树,最后按先序遍历右子树
         *
         * @param treeRoot 根结点
         */
        static void traverseTreeAccordingToInorder(TreeDate treeRoot) {
            if (treeRoot != null) {
                traverseTreeAccordingToInorder(treeRoot.leftData);
                nodeData(treeRoot);//显示结点数据
                traverseTreeAccordingToInorder(treeRoot.rightData);
            }
        }

        /**
         * 遍历二叉树(后序遍历算法)
         * 先按照后序遍历左子树,再按后序遍历右子树,最后访问根结点
         *
         * @param treeRoot 根结点
         */
        static void traverseTreeAccordingToPostorder(TreeDate treeRoot) {
            if (treeRoot != null) {
                traverseTreeAccordingToPostorder(treeRoot.leftData);
                traverseTreeAccordingToPostorder(treeRoot.rightData);
                nodeData(treeRoot);//显示结点数据
            }
        }
    }

调用:

        TreeDate rootNode = TreeDate.initTree("根结点");
        TreeDate.addNode(rootNode, "根结点", 1, "左结点1");
        TreeDate.addNode(rootNode, "根结点", 2, "右结点2");
        TreeDate.addNode(rootNode, "左结点1", 1, "左结点1.1");
        TreeDate.addNode(rootNode, "左结点1", 2, "右结点1.2");
        TreeDate.addNode(rootNode, "右结点2", 1, "左结点2.1");
        TreeDate.addNode(rootNode, "左结点2.1", 1, "左结点2.1.1");
        TreeDate node = TreeDate.findData(rootNode, "右结点2");
        if (node != null) {
            System.out.printf("找到右结点2,");
            TreeDate leftNode = TreeDate.leftNode(node);
            TreeDate rightNode = TreeDate.rightNode(node);
            if (leftNode != null) {
                System.out.printf("其左子树为:%s\t", leftNode.data);
            }
            if (rightNode != null) {
                System.out.printf("右子树为:%s", rightNode.data);
            }
            System.out.printf("%n");
        }
        int depth = TreeDate.depth(rootNode);
        System.out.printf("树深度:%d%n", depth);
        System.out.printf("按层遍历算法:%n");
        TreeDate.traverseTreeAccordingToLevel(rootNode);
        System.out.printf("%n先序遍历算法:%n");
        TreeDate.traverseTreeAccordingToPerorder(rootNode);
        System.out.printf("%n中序遍历算法:%n");
        TreeDate.traverseTreeAccordingToInorder(rootNode);
        System.out.printf("%n后序遍历算法:%n");
        TreeDate.traverseTreeAccordingToPostorder(rootNode);
        System.out.printf("%n");
        boolean isEmpty = TreeDate.isEmpty(rootNode);
        System.out.printf("是否是空树:%b%n", isEmpty);
        System.out.printf("清空树%n");
        TreeDate.clearTree(rootNode);

输出:

找到右结点2其左子树为:左结点2.1	
树深度:4
按层遍历算法:
 根结点-> 左结点1-> 右结点2-> 左结点1.1-> 右结点1.2-> 左结点2.1-> 左结点2.1.1->
先序遍历算法:
 根结点-> 左结点1-> 左结点1.1-> 右结点1.2-> 右结点2-> 左结点2.1-> 左结点2.1.1->
中序遍历算法:
 左结点1.1-> 左结点1-> 右结点1.2-> 根结点-> 左结点2.1.1-> 左结点2.1-> 右结点2->
后序遍历算法:
 左结点1.1-> 右结点1.2-> 左结点1-> 左结点2.1.1-> 左结点2.1-> 右结点2-> 根结点->
是否是空树false
清空树

5.栈结构

 

 

    /**
     * 定义数据结构中的元素
     */
    static class DataElement {
        String key;
        String other_messages;

        public DataElement(String key, String other_messages) {
            this.key = key;
            this.other_messages = other_messages;
        }
    }
    /**
     * 定义栈结构
     */
    static class Stack {
        static final int MAX_LENGTH = 10;//当前栈表的最大长度
        DataElement[] stackData = new DataElement[MAX_LENGTH];//栈表数据
        int top;//栈顶

        /**
         * 初始化栈
         */
        static Stack initStack() {
            Stack s = new Stack();
            s.top = 0;
            return s;
        }

        /**
         * 判断栈是否为空
         *
         * @param stack Stack
         * @return true是空栈,false栈不为空
         */
        static boolean isEmpty(Stack stack) {
            return stack.top == 0;
        }

        /**
         * 栈是不是已满
         *
         * @param stack Stack
         * @return true栈已满,false栈未满
         */
        static boolean isFull(Stack stack) {
            return stack.top == MAX_LENGTH;
        }

        /**
         * 清空栈
         *
         * @param stack Stack
         */
        static void clear(Stack stack) {
            stack.top = 0;
        }

        /**
         * 释放栈所占空间
         *
         * @param stack Stack
         */
        static void free(Stack stack) {
            if (stack != null) {
                stack = null;
            }
        }

        /**
         * 入栈
         *
         * @param stack   Stack
         * @param element DataElement
         * @return 0入栈失败,1入栈成功
         */
        static int push(Stack stack, DataElement element) {
            if (stack.top + 1 > MAX_LENGTH) {
                System.out.printf("栈溢出!");
                return 0;
            }
            stack.stackData[++stack.top] = element;//将element入栈,入栈后栈顶(top)+1
            return 1;
        }

        /**
         * 出栈
         *
         * @param stack Stack
         * @return 栈顶的DataElement
         */
        static DataElement pop(Stack stack) {
            if (stack.top == 0) {
                System.out.print("栈已空!");
                return null;
            }
            return stack.stackData[stack.top--];//将栈顶元素出栈,出栈后栈顶(top)-1
        }

        /**
         * 查看栈数据,由于栈只能操作栈顶数据,所以查看栈的数据只能看栈顶的元素
         *
         * @param stack Stack
         * @return 栈顶的DataElement
         */
        static DataElement peek(Stack stack) {
            if (stack.top == 0) {
                System.out.print("栈已空!");
                return null;
            }
            return stack.stackData[stack.top];
        }

    }

调用:

        Stack stack = Stack.initStack();
        System.out.printf("栈是不是空栈:%b%n", Stack.isEmpty(stack));
        DataElement element = new DataElement("s1", "s1_other");
        int push = Stack.push(stack, element);
        System.out.printf("入栈结果:%d%n", push);
        element = new DataElement("s2", "s2_other");
        push = Stack.push(stack, element);
        System.out.printf("入栈结果:%d%n", push);
        element = new DataElement("s3", "s3_other");
        push = Stack.push(stack, element);
        System.out.printf("入栈结果:%d%n", push);
        System.out.printf("栈是不是空栈:%b%n", Stack.isEmpty(stack));
        System.out.printf("栈是不是满栈:%b%n", Stack.isFull(stack));
        DataElement dataElement = Stack.peek(stack);
        assert dataElement != null;
        System.out.printf("查看栈:{%s,%s}%n", dataElement.key, dataElement.other_messages);
        dataElement = Stack.pop(stack);
        assert dataElement != null;
        System.out.printf("出栈:{%s,%s}%n", dataElement.key, dataElement.other_messages);
        dataElement = Stack.pop(stack);
        assert dataElement != null;
        System.out.printf("出栈:{%s,%s}%n", dataElement.key, dataElement.other_messages);
        Stack.clear(stack);
        System.out.printf("清空栈后栈是不是空栈:%b%n", Stack.isEmpty(stack));
        Stack.free(stack);//释放栈空间

输出:

栈是不是空栈true
入栈结果1
入栈结果1
入栈结果1
栈是不是空栈false
栈是不是满栈false
查看栈{s3,s3_other}
出栈{s3,s3_other}
出栈{s2,s2_other}
清空栈后栈是不是空栈true

6.图结构

 

 

 

 

 

 

图结构的代码参考 常用算法指南(三)查找算法 :5. 图结构中的查找算法

 

常用的数据结构基本就这些,下一章将会总结数学中的一些问题的算法,例如判断闰年,随机数生成算法,计算π的近似值等。

posted @ 2019-11-28 17:17  FFLYY  阅读(483)  评论(0编辑  收藏  举报