20172329 2018-2019-2 《Java软件结构与数据结构》实验二报告

20172329 2018-2019-2 《Java软件结构与数据结构》实验二报告

课程:《Java软件结构与数据结构》

班级: 1723

姓名: 王文彬

学号:20172329

实验教师:王志强

实验日期:2018年11月7日

必修/选修: 必修

一.实验内容

1.1 第一个实验内容

  • 要求
    • (1)参考课本P212使用链表实现二叉树进行对于课本代码的完善以及补全。
    • (2)实现方法getRight方法,contains方法,toString方法,preorder方法,postorder方法。
    • (3)用JUnit或自己编写驱动类对自己实现的LinkedBinaryTree进行测试
    • (4)提交测试代码运行截图,要全屏,包含自己的学号信息,上传代码至码云,并提交码云链接。

1.2 第二个实验内容

  • 要求
    • (1)基于LinkedBinaryTree,实现基于(中序,先序)序列构造唯一一棵二㕚树的功能。
    • (2)比如给出中序HDIBEMJNAFCKGL和前序ABDHIEJMNCFGKL,构造出附图中的树。
    • (3)用JUnit或自己编写驱动类对自己实现的类进行测试
    • (4)提交测试代码运行截图,要全屏,包含自己的学号信息,上传代码至码云,并提交码云链接。

1.3 第三个实验内容

  • 要求
    • (1)自己设计并实现一颗决策树
    • (2)用JUnit或自己编写驱动类对自己实现的类进行测试
    • (3)提交测试代码运行截图,要全屏,包含自己的学号信息,上传代码至码云,并提交码云链接

1.4 第四个实验内容

  • 要求
    • (1)输入中缀表达式,使用树将中缀表达式转换为后缀表达式,并输出后缀表达式和计算结果
    • (2)用JUnit或自己编写驱动类对自己实现的类进行测试
    • (3)提交测试代码运行截图,要全屏,包含自己的学号信息,上传代码至码云,并提交码云链接

1.5 第五个实验内容

  • 要求
    • (1)完成PP11.3:对于二叉树查找树的链表实现,请实现removeMax方法,findMin方法和findMax方法以及后绪方法。
    • (2)用JUnit或自己编写驱动类对自己实现的类进行测试
    • (3)提交测试代码运行截图,要全屏,包含自己的学号信息,上传代码至码云,并提交码云链接

1.6 第六个实验内容

  • 要求
    • (1)参考http://www.cnblogs.com/rocedu/p/7483915.html对Java中的红黑树(TreeMap,HashMap)进行源码分析,并在实验报告中体现分析结果。

二、实验过程及结果

2.1 第一个实验过程

  • 步骤:
    • (1)第一个实验是有关对于代码补全的实验实现,其中包括实现方法getRight方法,contains方法,toString方法,preorder方法,postorder方法。我们将一个一个进行实验并且进行代码分析。
  • 代码分析:

(1)getRight方法

        public LinkedBinaryTree<T> getRight()
    {
        LinkedBinaryTree node = new LinkedBinaryTree();
        node.root=root.getRight();
       // return new LinkedBinaryTree(root.getRight());
       return node;
    }
    

分析:该方法通过先进行一个新树的初始化,使得我们可以做一个临时树的角色,看到上述代码中注释的那一行,那是我第一次写的,是一个错误的代码,是因为他总是会得到根也就是root的右侧的树或结点,但是我们需要得到的右子树或者有结点是变化的,假如按照之前写的,就会导致无法进行递归,程序就死在根上了。我们通过初始化下面一行的代码进行根的更新,使我们得到合适的右子树。

(2)contains方法

    public boolean contains(T targetElement)
    {
        if (find(targetElement)==targetElement){
        return true;
    }
    else {
            return false;
        }

    }
    public T find(T targetElement) throws ElementNotFoundException
    {
        BinaryTreeNode<T> current = findNode(targetElement, root);

        if (current == null)
            throw new ElementNotFoundException("LinkedBinaryTree");

        return (current.getElement());
    }
    private BinaryTreeNode<T> findNode(T targetElement,
                                        BinaryTreeNode<T> next)
    {
        if (next == null)
            return null;

        if (next.getElement().equals(targetElement))
            return next;

        BinaryTreeNode<T> temp = findNode(targetElement, next.getLeft());

        if (temp == null)
            temp = findNode(targetElement, next.getRight());

        return temp;
    }

分析:该方法嵌套了另外的两个方法,写这个方法的逻辑就是,首先我们需要找到这个结点,假如找到了这个结点就返回true,假如没有找到就返回false。现在我们需要清楚查找这个结点的过程是如何进行的。首先,我们需要清楚我们需要从哪个结点开始找,在这里就有人有疑问了,为什么不总是设定成从根开始找,其实,假如像数据量小这样找还ok,但是假如在数据量庞大的时候,有这样一个设定可以很大的节约我们查找元素的速度。

(3)toString方法

 public String toString()
    {
        UnorderedListADT<BinaryTreeNode> nodes =
                new ArrayUnorderedList<BinaryTreeNode>();
        UnorderedListADT<Integer> levelList =
                new ArrayUnorderedList<Integer>();
        BinaryTreeNode current;
        String result = "";
        int printDepth = this.getHeight();
        int possibleNodes = (int)Math.pow(2, printDepth + 1);
        int countNodes = 0;

        nodes.addToRear(root);
        Integer currentLevel = 0;
        Integer previousLevel = -1;
        levelList.addToRear(currentLevel);

        while (countNodes < possibleNodes)
        {
            countNodes = countNodes + 1;
            current = nodes.removeFirst();
            currentLevel = levelList.removeFirst();
            if (currentLevel > previousLevel)
            {
                result = result + "\n\n";
                previousLevel = currentLevel;
                for (int j = 0; j < ((Math.pow(2, (printDepth - currentLevel))) - 1); j++)
                    result = result + " ";
            }
            else
            {
                for (int i = 0; i < ((Math.pow(2, (printDepth - currentLevel + 1)) - 1)) ; i++)
                {
                    result = result + " ";
                }
            }
            if (current != null)
            {
                result = result + (current.getElement()).toString();
                nodes.addToRear(current.getLeft());
                levelList.addToRear(currentLevel + 1);
                nodes.addToRear(current.getRight());
                levelList.addToRear(currentLevel + 1);
            }
            else {
                nodes.addToRear(null);
                levelList.addToRear(currentLevel + 1);
                nodes.addToRear(null);
                levelList.addToRear(currentLevel + 1);
                result = result + " ";
            }

        }

        return result;
    }

分析:这个代码其实就是教材中表达式树输出成一棵树的方法,具体详见我的第六周博客,在其中的课本问题三中有详细的分析。

(4)preorder方法

public Iterator<T> iteratorPreOrder()
    {
        ArrayUnorderedList<T> tempList = new ArrayUnorderedList<T>();
       preOrder(root, tempList);

        return new TreeIterator(tempList.iterator());
    }
    
protected void preOrder(BinaryTreeNode<T> node,
                            ArrayUnorderedList<T> tempList)
    {
        if(node!=null)
            {
               System.out.print(node.getElement()+" ");
                tempList.addToRear(node.getElement());
                preOrder(node.getLeft(),tempList);
                preOrder(node.getRight(),tempList);
            }

    }

分析:这一部分实则运用的是迭代器的方法,前序遍历实现讲分析与后序大致相同,在这里只分析前序,首先我们要清楚前序遍历的顺序,在这里假如有不清楚的同学可以参考我的第六周博客,教材内容总结部分。当我们清楚了前序遍历是如何进行的以后,我们就可以大致了解这个递归的运行原理了。其中迭代器的作用为的是我们可以将其进行一个一个的输出。

  • 实验实现结果展示:

2.2 第二个实验过程

  • 步骤:

    • 第二个实验是需要我们去利用中序和前序去构造出一颗二叉树,我们需要清楚的有以下步骤:

    • (1)首先我们在课上练习过如何去利用中序和前序去构造出一颗树,在这里我再进行说明;

    • (2)有这样一句话方便理解前(或后)序定根,中序定左右,如何理解这句话呢,举个例子,就拿我们题目中的进行分析。前序是:ABDHIEJMNCFGKL,中序是:HDIBEMJNAFCKGL。

      • 1、首先我们看前序的第一个元素,是A则其肯定是根,所以在中序中找到A,现在在中序遍历中A左边的就是A的左子树,A右边的就是A的右子树;

      • 2、同样我们继续找前序的第二个元素,是B,所以再看中序中B的位置,同样和理解A的左右同理,我们可以找到B结点的左子树和右子树。

      • 3、紧接着重复上述直到找到中序的第一个元素为至,我们就停止,现在我们构造好的是整个树的左子树(左树的左树);

      • 4、现在开始看A的左子树的右子树的完善工作,因为D是左边最后一个根,所以从中序中可以得知,H是D的左孩 子,I是D的右孩子。

      • 5、我们继续向上找,发现根是B,B的左边我们已经构造完成了,所以我们现在需要构造出B的右边,EMJN是我们现在需要构造出的,我们看中序,我们看到E是处于最左边的,说明它是一个左孩子或者是一个根,再紧接着看,前序,发现E是第一个,所以说明E是一个根,所以我们确定了B的右根为E;

      • 6、因为在看到中序E后面的是M,所以,和第5步的理解相同,发现同样M是一个根,现在再看前序,发现J和N在M的两侧,即分别是M的左子树和右子树。

      • 7、确定了A的左子树了以后我们继续看A的右子树,中序是:FCKGL,因为在前序中C在F之前,所以否定F是根,F是C的左子树,所以现在可以知道A的右子树的根是C,左子树是F,再根据同样的原理,可以得知,G为C的右根,KL分别是G的左孩子和右孩子。

      • 8、通过这样一个过程就构建出一棵树了。

    • (3)根据这样一个思想我们开始进行对于代码的编写。

  • 代码分析

public int find(T[] a,T element,int begin,int end){
        for(int i = begin;i<=end; i++){
            if(a[i]==element){
                return i;
            }
        }
        return -1;
    }

    public BinaryTreeNode<T> construct(T[] pre,int prestart,int preend,T[] inO,int inOSart,int inOend){
        if(prestart>preend||inOSart>inOend){
            return null;
        }
        BinaryTreeNode Root;
        T rootData = pre[prestart];
         Root = new BinaryTreeNode(rootData);
        //找到根节点所在位置
        int rootIndex = find(inO,rootData,inOSart,inOend);
        //构建左子树
        Root.left = construct(pre,prestart+1,prestart+rootIndex-inOSart,inO,inOSart,rootIndex-1);
       // 构建右子树

        Root.right = construct(pre,prestart+rootIndex-inOSart+1,preend,inO,rootIndex+1,inOend);
         return Root;

        }

        public void ConTree(T[] pre,T[] inO){
        BinaryTreeNode<T> node = construct(pre,0,pre.length-1,inO,0,inO.length-1);
        root=node;
        }

分析:我们在这个主方法定义了六个变量,在construct方法的作用分别为:传入一个先序遍历结果(数组),确定先序遍历中开始的位置,确定先序遍历中结束的位置,传入一个中序遍历结果(数组),确定中序遍历中开始的位置,确定中序遍历中结束的位置。现在我们开始看方法体的内容,我们首先确定一个东西,就是根肯定是传入先序遍历的第一个元素,所以先确定根的位置,找到根以后,我们开始安排找到它的子树们,我们通过一个find方法进行对于我们需要元素在中序遍历中的查找,因为我们可以通过一个规律,就是对于传入的元素查找后得到的值(1或者-1)进行对于左孩子还是右孩子的查找,从而可以完成一棵树的构造。

  • 实验实现结果展示

2.3 第三个实验过程

  • 步骤

    • 第三个实验是让我们自己写一个决策树,对于这个实验我基于课本代码进行了仿写进行实验。
  • 代码分析:

 public DecisionTree(String filename) throws FileNotFoundException
    {
        File inputFile = new File(filename);
        Scanner scan = new Scanner(inputFile);
        int numberNodes = scan.nextInt();
        scan.nextLine();
        int root = 0, left, right;
        
        List<LinkedBinaryTree<String>> nodes = new ArrayList<LinkedBinaryTree<String>>();
        for (int i = 0; i < numberNodes; i++)
            nodes.add(i,new LinkedBinaryTree<String>(scan.nextLine()));
        
        while (scan.hasNext())
        {
            root = scan.nextInt();
            left = scan.nextInt();
            right = scan.nextInt();
            scan.nextLine();
            
            nodes.set(root, new LinkedBinaryTree<String>((nodes.get(root)).getRootElement(), 
                                                     nodes.get(left), nodes.get(right)));
        }
        tree = nodes.get(root);
    }
    public void evaluate()
    {
        LinkedBinaryTree<String> current = tree;
        Scanner scan = new Scanner(System.in);

        while (current.size() > 1)
        {
            System.out.println (current.getRootElement());
            if (scan.nextLine().equalsIgnoreCase("N"))
                current = current.getLeft();
            else
                current = current.getRight();
        }

        System.out.println (current.getRootElement());
    }

分析:这一部分的代码因为都是书中的代码,自己就照搬了,(自己太懒了)在这代码中首先我们利用一个txt文档对于书中的文档进行了层序的保存,通过读取文件的方式我们进行决策树的构造,其中第一个方法(DecisionTree)我们将文件读取后将其内容一个一个放入一个链表,然后通过层序进行把元素放入树中;第二个方法(evaluate)用于判断当我们输入的是N或者其他时,进行对于元素的输出。

  • 实验实现结果展示

2.4 第四个实验过程

  • 步骤

    • 第四个实验要求我们输入一个中缀表达式,通过一个二叉树转换成后缀表达式,再将其输出后进行计算,首先我们需要清楚这个实验应该如何去进行。

    • (1)第四个实验一开始我画了很多中树图进行表示,最后自己认为假如实现的话有两种方式:

      • 1、因为输入的中缀表达式可能涉及我们需要面临括号的问题,或者也就是我们需要去如何去解决优先级的问题,在这时候我的第一种想法是:先将一个中缀表达式按照优先级进行排序,比如,1+(1+2✖3,我首先将1+2放到最前其次是 *3,最后是+1,最后结果就是1+2✖3+1 ,然后将数字与操作符存入两个栈或者链表,分别进行弹出,自左向右,形成一棵树;但是这个方法因为假如涉及两个括号就变得较为复杂,因为这个时候需要构建右子树,所以就将这个方法淘汰了。

      • 2、第二站方法也就是最终进行实现的方法,首先我们输入一个中缀表达式,将表达式的每一个元素进行拿出,按照“数字”,“括号”,“加减”,“乘除”分成四种情况,用两个栈或者链表进行保存“加减”和“构成树的结点”。这种方法会使得方法实现变得简单易行。

  • 代码分析

//因为代码的庞大,所以就关键代码进行分析,循环和输出的过程简单,在
//这里不做分析,在这里分析如何对于每一个元素进行分析
//这个代码参考了之前学长的代码,并且请教了学长本人,通过学长的讲授受益匪浅。
if (a[i].equals("(")){
    String temp1 = "";
    while (true) {
    if (!a[i+1].equals(")")){
     temp1 += a[i+1] + " ";
            }
            else {
            break;
        }
            i++;
            }
        chapter10.jsjf.LinkedBinaryTree temp = new chapter10.jsjf.LinkedBinaryTree();
        temp.root = maketree(temp1);
         num.add(temp);
         i++;
            }
         if (a[i].equals("+")||a[i].equals("-")){
     fuhao.add(String.valueOf(a[i]));
             }
      else if (a[i].equals("*")||a[i].equals("/")){
    LinkedBinaryTree left=num.remove(num.size()-1);
     String temp2=String.valueOf(a[i]);
     if (!a[i+1].equals("(")){
     LinkedBinaryTree right = new LinkedBinaryTree(String.valueOf(a[i+1]));
     LinkedBinaryTree node = new LinkedBinaryTree(temp2, left, right);
    num.add(node);
        }
        else {
        String temp3 = "";
        while (true) {
        if (!a[i+1].equals(")")){
        temp3+=String.valueOf(a[i+1]);
        }
        else {
         break;
        }
        i++;
        }
        LinkedBinaryTree temp4 = new LinkedBinaryTree();
        temp4.root = maketree(temp3);
        LinkedBinaryTree node1 = new LinkedBinaryTree(temp2, left, temp4);
        num.add(node1);
       }
       }else {
        num.add(new LinkedBinaryTree(a[i]));}            

分析:从上述的代码就可以看到需要的逻辑之复杂,当时在学习过程就实验四真的是绞尽脑汁。首先,我们需要做的事情是先了解我们这个树是如何“长”成一棵树的,因为优先级最高的是括号,所以括号的这一部分就是我们的叶子,我们这个树是倒着长,从叶子长到根,因此我们就需要针对括号进行优先处理,所以我们先写括号。1、当我们遇到‘(’的时候,我们要做的一件事就是需要将直至‘)’内的元素都进行一个保存,因为说明这一部分我们需要优先处理它,当我们将这一部分进行保存了以后我们利用一个递归,开始处理这一部分;2、处理括号内部分的过程和处理括号外是一样的,只是括号需要优先处理,现在我们开始分析当我们进行括号或者一个类似与‘1+2✖6’之类的式子进行处理;3、当我们遇到数字的时候,我们将其保存进数字的链表(树类型,也就是保存进一个以这个数字为根的小树)中,然后循环;4、当我们遇到‘+或者-’的时候,将其保存进一个存符号的链表,然后循环;5、当我们遇到‘✖或者➗的时候,我们首先需要将我们之前存入数字的链表的元素进行拿出并且进行将其做一个新树,因为在画过这种树图的同学来讲,很清楚一个问题,就是加法肯定会跟靠近树根,所以我们要将我们之前放入数字链表的元素拿出来,为做一个左树做好准备,然后判断一下‘✖️或者➗’ 后有没有括号,假如有括号我们仍旧需要优先处理,按照1,2操作,,构造好一个右树,然后当我们处理好这一系列问题以后,就可以以这个+或者-为根,将刚刚分析的做好准备的左树和刚刚构造好的右树进行放在这个根的下面,分别做它的左子树和右子树,然后重复这个过程,直至没有元素可找。这就是整个实验四在遇到各个符号的情况。

  • 实验实现结果展示

2.5 第五个实验过程

  • 步骤:

    • (1)第五个实验同时我们和第一个实验四相似,都是进行代码补全,进行分析。

    • (2)实现removeMax方法,findMin方法和findMax方法以及后绪方法。

  • 代码分析:

(1)removeMax方法

public T removeMax() throws EmptyCollectionException
    {
        T result = null;

        if (isEmpty())
            throw new EmptyCollectionException("LinkedBinarySearchTree");
        else
        {
            if (root.right == null)
            {
                result = root.element;
                root = root.left;
            }
            else
            {
                BinaryTreeNode<T> parent = root;
                BinaryTreeNode<T> current = root.right;
                while (current.right != null)
                {
                    parent = current;
                    current = current.right;
                }
                result =  current.element;
                parent.right = current.left;
            }

            modCount--;
        }

        return result;
    }

因为我们知道对于一个树而言,最左边是最小值,最右边是最大值,所以我们假如要删除最小值的话,就需要先找到这个最小值,然后让它为空,并且返回它,删除最大值也是同样的道理。而找到最小值最大值同样也只是这一部分代码的一部分,同理。

  • 实验实现结果展示

2.6 第六个实验过程

  • 步骤:

    • 最后一个实验是让我们对Java中的红黑树(TreeMap,HashMap)进行源码分析,首先既然是红黑树的两个方法,所以在开始的时候我们要去了解红黑树是什么,具体可以详见我的第七周博客

    • 首先既然两个都是map结尾的,说明map也是一个类,我们先来看看map是什么?

    • map:Map接口中键和值一一映射. 可以通过键来获取值。

      • (1)给定一个键和一个值,你可以将该值存储在一个Map对象. 之后,你可以通过键来访问对应的值。
      • (2)当访问的值不存在的时候,方法就会抛出一个NoSuchElementException异常.
      • (3)当对象的类型和Map里元素类型不兼容的时候,就会抛出一个 ClassCastException异常。
      • (4)当在不允许使用Null对象的Map中使用Null对象,会抛出一个NullPointerException 异常。
      • (5)当尝试修改一个只读的Map时,会抛出一个UnsupportedOperationException异常。
      • 用代码举个例子:
import java.util.*;

public class CollectionsDemo {

   public static void main(String[] args) {
      Map m1 = new HashMap(); 
      m1.put("Zara", "8");
      m1.put("Mahnaz", "31");
      m1.put("Ayan", "12");
      m1.put("Daisy", "14");
      System.out.println();
      System.out.println(" Map Elements");
      System.out.print("\t" + m1);
   }
}
结果:
Map Elements
        {Mahnaz=31, Ayan=12, Daisy=14, Zara=8}
  • 在以上的程序中同样的也展示出了我们一会儿要分析的方法之一hashmap方法,以上的方法是用了其put方法,也就是让两个参数之间建立了一种映射。因此在这里我们详细的列一下map这个类所拥有的方法。
序号 方法 描述
1 void clear( ) 从此映射中移除所有映射关系(可选操作)。
2 boolean containsKey(Object k) 如果此映射包含指定键的映射关系,则返回 true。
3 boolean containsValue(Object v) 如果此映射将一个或多个键映射到指定值,则返回 true。
4 Set entrySet( ) 返回此映射中包含的映射关系的 Set 视图。
5 boolean equals(Object obj) 比较指定的对象与此映射是否相等。
6 Object get(Object k) 返回指定键所映射的值;如果此映射不包含该键的映射关系,则返回 null。
7 int hashCode( ) 返回此映射的哈希码值。
8 boolean isEmpty( ) 如果此映射未包含键-值映射关系,则返回 true。
9 Set keySet( ) 返回此映射中包含的键的 Set 视图。
10 Object put(Object k, Object v) 将指定的值与此映射中的指定键关联(可选操作)。
11 void putAll(Map m) 从指定映射中将所有映射关系复制到此映射中(可选操作)。
12 Object remove(Object k) 如果存在一个键的映射关系,则将其从此映射中移除(可选操作)。
13 int size( ) 返回此映射中的键-值映射关系数。
14 Collection values( ) 返回此映射中包含的值的 Collection 视图。
  • 在了解了这个map类以后我们开始步入我们的正题。

  • treemap方法:

    • 我们首先先了解一下这个类:

      • 1、TreeMap 是一个有序的key-value集合,它是通过红黑树实现的。
      • 2、TreeMap 继承于AbstractMap,所以它是一个Map,即一个key-value集合。
      • 3、TreeMap 实现了NavigableMap接口,意味着它支持一系列的导航方法。比如返回有序的key集合。
      • 4、TreeMap 实现了Cloneable接口,意味着它能被克隆。
      • 5、TreeMap 实现了java.io.Serializable接口,意味着它支持序列化。
      • 6、TreeMap基于红黑树(Red-Black tree)实现。该映射根据其键的自然顺序进行排序,或者根据创建映射时提供的 Comparator 进行排序,具体取决于使用的构造方法。
      • 7、TreeMap的基本操作 containsKey、get、put 和 remove 的时间复杂度是 log(n) 。
      • 8、TreeMap是非同步的。 它的iterator 方法返回的迭代器是fail-fastl的。
    • 源码分析
      我在这里通过网上其他博主的参考和自己的理解对此方法进行了分析,因为代码的庞大,所以在这里放置代码链接。重要的一些方法在这里我进行分析。

(1)了解构造函数

// 带比较器的构造函数
    public TreeMap(Comparator<? super K> comparator) {
        this.comparator = comparator;
    }

    // 带Map的构造函数,Map会成为TreeMap的子集
    public TreeMap(Map<? extends K, ? extends V> m) {
        comparator = null;
        putAll(m);
    }

    // 带SortedMap的构造函数,SortedMap会成为TreeMap的子集
    public TreeMap(SortedMap<K, ? extends V> m) {
        comparator = m.comparator();
        try {
            buildFromSorted(m.size(), m.entrySet().iterator(), null, null);
        } catch (java.io.IOException cannotHappen) {
        } catch (ClassNotFoundException cannotHappen) {
        }
    }

(2)了解我们在其他数据结构中常见的几个方法

 // 返回TreeMap中是否保护“键(key)”
    public boolean containsKey(Object key) {
        return getEntry(key) != null;
    }

    // 返回TreeMap中是否保护"值(value)"
    public boolean containsValue(Object value) {
        // getFirstEntry() 是返回红黑树的第一个节点
        // successor(e) 是获取节点e的后继节点
        for (Entry<K,V> e = getFirstEntry(); e != null; e = successor(e))
            if (valEquals(value, e.value))
                return true;
        return false;
    }

    // 获取“键(key)”对应的“值(value)”
    public V get(Object key) {
        // 获取“键”为key的节点(p)
        Entry<K,V> p = getEntry(key);
        // 若节点(p)为null,返回null;否则,返回节点对应的值
        return (p==null ? null : p.value);
    }

(3)其他详见码云

  • hashmap方法
    • 一开始看到这个方法有一丝疑问,为什么在红黑树里面还会牵扯到哈希表,也就是散列表,然后就心存一位是不是应该是treeset方法,所以一会儿还是想继续分析treeset方法,但是首先我需要了解,为什么散列表会和红黑树挂勾呢?

    • 我们知道HashMap中的值都是key,value对吧,其实这里的存储与上面的很像,key会被映射成数据所在的地址,而value就在以这个地址为头的链表中,这种数据结构在获取的时候就很快。但这里存在的问题就是如果hash桶较小,数据量较大,就会导致链表非常的长。比如说上面的长为11的空间我要放1000个数,无论Hash函数如何精妙,后面跟的链表都会非常的长,这样Hash表的优势就不复存在了,反而倾向于线性检索。

    • 源码分析

//实际存储的key-value键值对的个数
transient int size;
//阈值,当table == {}时,该值为初始容量(初始容量默认为16);当table被填充了,也就是为table分配内存空间后,threshold一般为 capacity*loadFactory。HashMap在进行扩容时需要参考threshold,后面会详细谈到
int threshold;
//负载因子,代表了table的填充度有多少,默认是0.75
final float loadFactor;
//用于快速失败,由于HashMap非线程安全,在对HashMap进行迭代时,如果期间其他线程的参与导致HashMap的结构发生变化了(比如put,remove等操作),需要抛出异常ConcurrentModificationException
transient int modCount;

  • 构造函数:
public HashMap(int initialCapacity, float loadFactor) {
     //此处对传入的初始容量进行校验,最大不能超过MAXIMUM_CAPACITY = 1<<30(230)
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " +
                                               initialCapacity);
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " +
                                               loadFactor);

        this.loadFactor = loadFactor;
        threshold = initialCapacity;
     
        init();//init方法在HashMap中没有实际实现,不过在其子类如 linkedHashMap中就会有对应实现
    }
  • get方法:
public V get(Object key) {
     //如果key为null,则直接去table[0]处去检索即可。
        if (key == null)
            return getForNullKey();
        Entry<K,V> entry = getEntry(key);
        return null == entry ? null : entry.getValue();
 }
  • treeset方法:

  • 构造函数:

序号 方法 描述
1 TreeSet () 此构造函数构造空树集,将在根据其元素的自然顺序按升序排序。
2 TreeSet (Collection c) 此构造函数生成树的集合,它包含的元素的集合 c。
3 TreeSet (Comparable comp) 此构造函数构造一个空树集,将根据给定的比较器进行排序。
4 TreeSet (SortedSet ss) 此构造函数生成包含给定 SortedSet 的元素 TreeSet
  • 方法:
序号 方法 描述
1 add(E e) 将指定的元素添加到这套,如果它已不存在。
2 addAll(Collection<? extends E> c) 在加入这一组指定的集合中添加的所有元素。
3 ceiling(E e) 返回最小的元素在这一组大于或等于给定的元素
4 clear() 从这一组中移除所有元素。
5 clone() 返回此TreeSet实例浅表副本。
6 comparator() 返回用于排序在这集,或空元素
7 contains(Object o) 如果此集合包含指定的元素,则返回true 。
8 descendingIterator() 返回迭代器中这套降序排序的元素。
9 descendingSet() 返回逆序视图中包含的元素这一套。
10 first() 返回第一个 (最低) 元素当前在这一套。
11 floor(E e) 返回的最大元素在这一组小于或等于null如果没有这样的元素。
12 headSet(E toElement) 返回其元素是严格小于toElement这套的部分视图.
13 headSet(E toElement, boolean inclusive) 返回一个视图的这部分设置的元素都小于
14 higher(E e) 返回最小的元素在这套严格大于给定的元素
15 isEmpty() 如果此集不包含任何元素,则返回true 。
16 iterator() 返回迭代器中这套以升序排序的元素。
17 last() 在这套目前返回的最后一个 (最高) 的元素。
18 lower(E e) 在这一套严格的小于给定的元素,则null返回的最大元素
19 pollFirst() 检索和删除第一个 (最低) 元素,或如果此集合为空
20 pollLast() 检索和删除的最后一个 (最高) 的元素
21 remove(Object o) 从这一组中移除指定的元素,如果它存在。
22 size() 在这套 (其基数) 中返回的元素的数目。
23 subSet(E fromElement, boolean fromInclusive, E toElement) 返回此集的部分视图的元素范围从fromElement到toElement.

三、 实验过程中遇到的问题和解决过程

  • 问题1:在之前写第二个实验的时候发生了栈溢出的情况,如下

java.lang.StackOverflowError异常(异常的图丢了)

  • 问题1解决方案:

    • (1)我发现当我们空写一个初始化的时候,用它递归的时候就会发生栈溢出。

    • (2)方法一:用栈把递归转换成非递归
      通常,一个函数在调用另一个函数之前,要作如下的事情:

      • a)将实在参数,返回地址等信息传递给被调用函数保存;

      • b)为被调用函数的局部变量分配存储区;

      • c)将控制转移到被调函数的入口。从被调用函数返回调用函数之前,也要做三件事情:

        • a)保存被调函数的计算结果;
        • b)释放被调函数的数据区;
        • c)依照被调函数保存的返回地址将控制转移到调用函数.所有的这些,不论是变量还是地址,本质上来说都 是"数据",都是保存在系统所分配的栈中的. 那么自己就可以写一个栈来存储必要的数据,以减少系统负。
    • (3)方法二:使用static对象替代nonstatic局部对象
      在递归函数设计中,可以使用static对象替代nonstatic局部对象(即栈对象),这不仅可以减少每次递归调用和返回时产生和释放nonstatic对象的开销,而且static对象还可以保存递归调用的中间状态,并且可为各个调用层所访问。

  • 问题2:其余的问题在我搞懂实验的同时也都解决了,很多都写在上述的步骤过程和代码分析中进行了分析。

  • 问题2:详见实验过程

四、感想

在这次实验中,自己写了很多,同时也毙掉了很多版代码,算法真是个神奇的东西,我自己也参考了很多文章,很多博客才能完成这次实验,发现自己还差很多,自己还需要很努力才行。

五、参考文献

【数据结构】中缀表达式构造二叉树转换成后缀表达式
表达式树—中缀表达式转换成后缀表达式(一)
java实现二叉树已知先序遍历和中序遍历求后序遍历
根据中序和前序序列生成二叉树java递归实现
已知二叉树的中序和前序序列(或后序)求解树
Java实现二叉树的前序、中序、后序、层序遍历(非递归方法)
蓝墨云班课
Java程序设计
Java 集合系列12之 TreeMap详细介绍(源码解析)和使用示例
Java Map 接口
HashMap实现原理及源码分析

posted @ 2018-11-11 11:23  lalalaouye  阅读(223)  评论(0编辑  收藏  举报