数据结构的梳理

       说到数据结构,一般面试的时候经常会问这个问题,这个属于很基本的知识点,但是往往平时开发的时候自然而然的会用,面试需要你系统说的时候可能就卡壳了,今天打算把它系统整理下,以后也可以很好的表述出来。。。

说到开发:

        程序开发 = 数据结构 + 算法;

用这个公式可以大体表述我们开发的架构,所以数据结构是开发的基础框架。

一、官方概念:

       数据结构是一门研究非数值计算的程序设计问题中的操作对象,以及它们之间的关系和操作等相关问题的学科。

      可以理解为是数据元素之间存在的关系的集合。

二、数据结构的细分:

       数据结构中的具体结构是我们需要学习和研究的主体对象,因为数据在内存中的表现形式都是二进制。传统上,我们把数据结构分为逻辑结构和物理结构。

(一)、逻辑结构:反应元素之间的逻辑关系,即元素前后之间的关系,和存储位置没有任何关系。

·1、集合结构

       集合结构中的元素属于同一个集合中,它们的关系是并列关系。

2、线性结构

      线性结构中的元素存在一对一的关系。

3、树形结构

      树形结构中的元素一般存在一对多的关系。

4、图形结构

      图形结构中的元素存在着多对多的相互关系。

(二)、物理结构:又叫存储结构。指数据在在计算机存储空间的存储形式,即数据在存储器中的存放形式,存储器主要针对内存而言,硬盘、光盘、软盘等外部存储器的数据组织常用文件结构来描述。

1、顺序存储结构

     顺序存储结构是把数据元素存储在连续的数据单元里,其数据间的逻辑关系和物理关系是一致的。典型:数组。

2、链式存储结构

      链式存储结构是把数据元素存储在非连续的数据单元里,即把数据存储在内存的任意的位置,这些数据在内存中的地址可以是连续的也可以是不连续的。它是通过指针来找到对应的元素。

三、java中常用的数据结构

1、线性表

      线性表在内存中是一块连续的存储空间;在线性表中访问元素的速度很快,但是在添加或者删除元素的时候,改变线性表的长度,它会在移动元素上耗费大量的时间。线性表最直接的代表是:数组,ArrayList

2、链式表

     链表的存储是链式的,它不强迫数据是在一片连续的内存空间;它可以是分散存储的。所以,它的每个元素除了包括元素的值外,还要包括一些额外的信息;如:它的下一个元素在什么地方。

      最基本的链式存储中的元素包括两部分:元素值下个元素的位置;JAVA 中对链表的实现是通过类:LinkedList来实现的。  (这篇博客关于链表的例子解释很清楚,可以看看

3、栈

     栈是一种只能在一端进行插入和删除操作的特殊线性表。

     其中,允许插入和删除的一端称为栈顶,另一端称为栈底。通常,将栈的插入操作称为入栈,删除操作称为出栈。入栈时元素总是放在栈底,而出栈的总是栈顶元素。因此,栈中元素采用的是“后进先出”的方式。

      栈的数据元素类型可以任意,只要是同一种类型即可。它的基本操作包括清空、判空、求元素个数、获取栈顶、入栈和出栈等。

public class StackTest {
    private Object[] stacks;//栈内的元素数组
    private int top;//栈顶的指针值

    public StackTest(int maxSize) {
        stacks = new Object[maxSize];
        top = 0;
    }

    /**
     * 清楚栈内元素
     */
    public void clear() {
        top = 0;
    }

    /**
     * 栈内元素是否为空
     *
     * @return
     */
    public boolean isEmpty() {
        return top == 0;
    }

    /**
     * 栈内元素的个数
     *
     * @return
     */
    public int length() {
        return top;
    }

    /**
     * 栈顶元素
     *
     * @return
     */
    public Object peek() {
        if (!isEmpty()) {
            return stacks[top - 1];
        } else {
            return null;
        }
    }

    /**
     * 入栈
     *
     * @param e
     */
    public void push(Object e) throws Exception {
        if (top == stacks.length) {
            throw new Exception("栈已满");
        } else {
            stacks[top++] = e;
        }
    }

    /**
     * 出栈
     *
     * @return
     * @throws Exception
     */
    public Object pop() throws Exception {
        if (top == 0) {
            throw new Exception("栈已空");
        } else {
            return stacks[--top];
        }
    }

}

4、队列

     队列(queue)也是一种线性表,它的特性是先进先出,插入在一端,删除在另一端。就像排队一样,刚来的人入队(push)要排在队尾(rear),每次出队(pop)的都是队首(front)的人。

     队列(Queue)与栈一样,是一种线性存储结构,它具有如下特点:

     1)队列中的数据元素遵循“先进先出”(First In First Out)的原则,简称FIFO结构。

     2)在队尾添加元素,在队头删除元素。

      队列和栈的代码可以查看这个博客

5、串

      串(string)是由零个或多个宇符组成的有限序列,又名叫字符串。

      串中的字符数目n称为串的长度,定义中谈到“有限”是指长度n是一个有限的数值。零个字符的串称为空串(null string),它的长度为零,可以直接用两双引号一表示,也可以用希腊Φ字母来表示。所谓的序列,说明串的相邻字符之间具有前驱和后继的关系。

      空格串,是只包含空格的。注意它与空串的区别,空格串是有内容有长度的,而且可以不止一个空格。

     子串与主串,串中任意个数的连续字符组成的子序列称为该串的子串,相应地,包含子串的串称为主串。

     子串在主串中的位置就是子串的第一个字符在主串中的序号。

 6、数组

      数组是用来存放同一种数据类型的集合,注意只能存放同一种数据类型(Object类型数组除外)。

   数组的局限性分析:

  ①、插入快,对于无序数组,上面我们实现的数组就是无序的,即元素没有按照从大到小或者某个特定的顺序排列,只是按照插入的顺序排列。无序数组增加一个元素很简单,只需要在数组末尾添加元素即可,但是有序数组却不一定了,它需要在指定的位置插入。

  ②、查找慢,当然如果根据下标来查找是很快的。但是通常我们都是根据元素值来查找,给定一个元素值,对于无序数组,我们需要从数组第一个元素开始遍历,直到找到那个元素。有序数组通过特定的算法查找的速度会比无需数组快,后面我们会讲各种排序算法。

  ③、删除慢,根据元素值删除,我们要先找到该元素所处的位置,然后将元素后面的值整体向前面移动一个位置。也需要比较多的时间。

  ④、数组一旦创建后,大小就固定了,不能动态扩展数组的元素个数。如果初始化你给一个很大的数组大小,那会白白浪费内存空间,如果给小了,后面数据个数增加了又添加不进去了。

  很显然,数组虽然插入快,但是查找和删除都比较慢,而且扩展性差,所以我们一般不会用数组来存储数据,那有没有什么数据结构插入、查找、删除都很快,而且还能动态扩展存储个数大小呢,答案是有的,但是这是建立在很复杂的算法基础上。
 

7、广义表

     广义表是由零个或多个原子或子表组成的优先序列,是线性表的推广。区别在于:线性表的元素仅限于原子项;而广义表的元素即可以是原子项,也可以是广义表。

     具有递归特性:由表头、表尾的定义可知:任何一个非空广义表其表头可能是原子,也可能是列表,而其表尾必定是列表。每个表尾又可以继续分为表头,表尾……

8、树和二叉树

      二叉树:每个结点至多只有两棵子树(即二叉树中不存在度大于2的结点),并且,二叉树的子树有左右之分,其次序不能任意颠倒。

      满二叉树:2^k-1个结点的深度为K的二叉树。

     完全二叉树:一棵具有n个结点的二叉树的结构与满二叉树的前n个结点的结构相同,这样的二叉树称作完全二叉树。即树的结点对应于相同深度的满二叉树。

      基本操作:树的主要操作有
(1)创建树IntTree(&T)
(2)销毁树DestroyTree(&T)
(3)构造树CreatTree(&T,deinition)
(4)置空树ClearTree(&T)
(5)判空树TreeEmpty(T)
(6)求树的深度TreeDepth(T)
(7)获得树根Root(T)
(8)获取结点Value(T,cur_e,&e),将树中结点cur_e存入e单元中。
(9)数据赋值Assign(T,cur_e,value),将结点value,赋值于树T的结点cur_e中。
(10)获得双亲Parent(T,cur_e),返回树T中结点cur_e的双亲结点。
(11)获得最左孩子LeftChild(T,cur_e),返回树T中结点cur_e的最左孩子。
(12)获得右兄弟RightSibling(T,cur_e),返回树T中结点cur_e的右兄弟。
(13)插入子树InsertChild(&T,&p,i,c),将树c插入到树T中p指向结点的第i个子树之前。
(14)删除子树DeleteChild(&T,&p,i),删除树T中p指向结点的第i个子树。
(15)遍历树TraverseTree(T,visit())

1、链式存储的二叉树的基本操作以及算法实现
     因为顺序存储只适用于满二叉树,如果存储完全二叉树,会造成空间的大量浪费,比如最坏情况下,k度的完全二叉树每个节点只有左子树存在,这样将有2^k-k-1个节点存储空值。所以选用链表存储二叉树更合适。
树的链式存储结构可抽象出来,Java语言描述如下:

 public class TreeNode {
        int data;
        TreeNode leftNode;
        TreeNode rightNode;

        public TreeNode() {
        }

        public TreeNode(int data) {
            this.data = data;
            this.leftNode = null;
            this.rightNode = null;
        }

        public TreeNode(int data, TreeNode leftNode, TreeNode rightNode) {
            this.data = data;
            this.leftNode = leftNode;
            this.rightNode = rightNode;
        }
    }

    //实现二叉链表结构,建立二叉树
    public TreeNode createTree() {
        int data[] = {1, 2, 0, 0, 3, 4, 5, 0, 6, 7, 8, 0, 0, 9};
        TreeNode tree;
        for (int i = 0; i < data.length; i++) {
            tree = new TreeNode(data[i]);
            tree.leftNode = createTree();
            tree.rightNode = createTree();
        }
        return tree;
    }

2、二叉树的遍历操作
     如果遵循先左后右的规则,那么二叉树的遍历方法可分为三种:先根序遍历;中根序遍历;后根序遍历。

 //先根序的递归方法遍历 
    public void disp(TreeNode tree) {
        if (tree != null) {
            System.out.print(tree.data);
            disp(tree.leftNode);
            disp(tree.rightNode);
        }
    }

    //中根序的递归方法遍历 
    public void disp(TreeNode tree) {
        if (tree != null) {
            disp(tree.leftNode);
            System.out.print(tree.data);
            disp(tree.rightNode);
        }
    }

    //后根序的递归方法遍历
    public void disp(TreeNode tree) {
        if (tree != null) {
            disp(tree.rightNode);
            System.out.print(tree.data + " ");
            disp(tree.leftNode);
        }
    }

四、Android中常用的数据结构

       Android中一般使用的数据结构有Java中基础数据结构Set,List,Map,还有Android特有的,不如SparseArray。数据结构图,表述如下:

      

      Collection 是所有集合类的接口,Set,List都实现Collection接口。Collection元素迭代:

  /**
     * 迭代访问集合中的子元素
     */
    private void test() {
        Iterator it = collections.iterator();//collections 就是需要遍历的元素集合
        while (it.hasNext()) {
            Object next = it.next();//得到下一个元素
        }
    }

1、Set

      Set 一般使用是TreeSet 和 HashSet,Set是一个不能包含重复元素的集合。

1)、TreeSet

       根据二叉树实现,放入数据不能重复且不能为null,可以重写compareTo()方法来确定元素大小,从而进行升序排序。

 /**
  * TreeSet的集合创建
  */ 
public void createSet() {
        Set<Integer> sets = new TreeSet<>(new MyOrderComparator());
        sets.add(1);
        sets.add(2);
        sets.add(4);

        for (Integer inn : sets) {
            LogUtils.e("元素输出: " + inn);//结果:1 2 4
        }
    }

    static class MyOrderComparator implements Comparator<Integer> {

        @Override
        public int compare(Integer o1, Integer o2) {
            if (o1 < o2) {
                return -1;
            }
            if (o1 == o2) {
                return 0;
            }
            if (o1 > o2) {
                return 1;
            }
            return 0;
        }
    }

2)、HashSet

       根据hashCode来决定存储位置,所以必须实现hashCode()方法,存储数据是无序的且不能重复,可以存储null,但是有且仅有一个。

    public void createSet() {
        Set<String> sets = new HashSet<>();
        sets.add("orange");
        sets.add("apple");
        sets.add("orange");
        sets.add("orange");
        sets.add("orange");
        sets.add(null);
        sets.add("apple");
        sets.add("orange");
        sets.add(null);
        sets.add(null);
        sets.add(null);
        sets.add(null);
        for (String inn : sets) {
            LogUtils.e("元素输出: " + inn);//输出结构:orange apple  null
        }
    }

2、List

     List比较常用的是ArrayList 和 LinkedList,还有一个Vector,不太常用。

 未完待续

猜你喜欢

转载自blog.csdn.net/cherry459/article/details/82146306