线性表,树,堆

175 阅读18分钟

1.线性表:

  • 线性表是最基本,最简单,也是最常用的一种数据结构。一个线性表是n个具有相同特征的数据元素的有限序列

  • 线性表的分类

    • 顺序存储:
    • 链式存储:

1.顺序表:

  • 查询快,增删改慢

2.链表:

1.单向链表:

image.png

  • 结点API设计

    类名Node
    构造方法Node(T t,Node next):创建Node对象
    成员变量T item:存储数据 Node next:指向下一个结点
  • 单向链表API设计:

    类名LinkList
    构造方法LinkList () : 创建LinkList对象
    成员方法
    成员内部类private class Node : 结点类
    成员变量private Node head : 记录首结点 private int N : 记录链表的长度
  • 结点类实现

    public class Node<T> {
        //存储元素
        public T item;
        //指向下一个结点
        public Node next;
    
        public Node(T item,Node next){
            this.item=item;
            this.next=next;
        }
    }
    
  • 生成链表:

    public static void main(String[] args) {
        //构建结点
        Node<Integer> first=new Node<>(1,null);
        Node<Integer> second=new Node<>(2,null);
        Node<Integer> third=new Node<>(3,null);
        //生成链表
    first.next=second;second.next=third;
    }
    
  • 单向链表的代码实现

    public class LinkList<T> implements Iterable<T>{
        //记录头结点
        private Node head;
        //记录链表的长度
        private int N;
    
        public LinkList(){
            //初始化头结点
            head=new Node(null,null);
            N=0;
        }
    
        //清空列表
        public void clear(){
            head.next=null;
            head.item=null;
            //头结点不算长度
            N=0;
        }
        //获取链表的长度
        public int length(){
            return N;
        }
        //判断链表是否为空
        public boolean isEmpty(){
            return N==0;
        }
        //获取指定位置i出的元素
        public Object get(int i){
            if (i<0||i>=N){
                throw new RuntimeException("位置不合法!");
            }
            Node n=head.next;
            for (int index = 0; index < i; index++) {
                n = n.next;
            }
            return n.item;
        }
        //向链表中添加元素t
        public void insert(T t){
            //找到最后一个节点
            Node n = head;
            while(n.next!=null){
                n = n.next;
            }
            Node newNode = new Node(t, null);
            //让当前最后一个结点指向新结点
            n.next = newNode;
            //链表长度+1
            N++;
        }
        //向指定位置i处,添加元素t
        public void insert(int i,T t){
            if (i<0||i>N){
                throw new RuntimeException("位置不合法!");
            }
            //寻找位置i之前的结点
            Node pre = head;
            for (int index = 0; index <=i-1; index++) {
                pre = pre.next;
            }
            //位置i的结点
            Node curr = pre.next;
            //构建新的结点,让新结点指向位置i的结点
            Node newNode = new Node(t, curr);
            //让之前的结点指向新结点
            pre.next = newNode;
            //长度+1
            N++;
        }
        //删除指定位置i处的元素,并返回被删除的元素
        public Object remove(int i){
            if (i<0 || i>N){
                throw new RuntimeException("位置不合法");
            }
            //寻找i之前的元素
            Node pre = head;
            for (int index = 0; index <=i-1; index++) {
                pre = pre.next;
            }
            //当前i位置的结点
            Node curr = pre.next;
            //前一个结点指向下一个结点,删除当前结点
            pre.next = curr.next;
            //长度-1
            N--;
            return curr.item;
        }
        //查找元素t在链表中第一次出现的位置
        public int indexOf(T t){
            Node n = head;
            for (int i = 0;n.next!=null;i++){
                n = n.next;
                if (n.item.equals(t)){
                    return i;
                }
            }
            return -1;
        }
        //结点类
        private class Node{
            //存储数据
            T item;
            //下一个结点
            Node next;
            public Node(T item, Node next) {
                this.item = item;
                this.next = next;
            }
        }
    
        @Override
        public Iterator<T> iterator() {
            return (Iterator<T>) new LIterator();
        }
        private class LIterator implements Iterator<Object> {
            private LinkList.Node n;
            public LIterator() {
                this.n = head;
            }
            @Override
            public boolean hasNext() {
                return n.next!=null;
            }
            @Override
            public Object next() {
                n = n.next;
                return n.item;
            }
        }
    }
    
  • 测试:

    public class Test {
        public static void main(String[] args) {
            LinkList<String> list = new LinkList<>();
            list.insert(0,"张三");
            list.insert(1,"李四");
            list.insert(2,"王五");
            list.insert(3,"赵六");
            for (String s : list) {
                System.out.println(s);
            }
        }
    }
    

2.双向链表:

image.png

  • 结点API设计

    类名Node
    构造方法Node(T t,Node pre,Node next):创建Node对象
    成员变量T item:存储数据 Node next:指向下一个结点 Node pre:指向上一个结点
  • 双向链表API设计:

    类名TowWayLinkList
    构造方法TowWayLinkList():创建TowWayLinkList对象
    成员方法.........
    成员内部类private class Node:结点类
    成员变量1.private Node fifirst:记录首结点 2.private Node last:记录尾结点 3.private int N:记录链表的长度
  • 结点类实现:

    public class Node<T> {
        //存储元素
        public T item;
        //指向下一个结点
        public Node next;
        //指向上一个结点
        public Node pre;
    
        //创建Node对象
        public Node(T item,Node pre,Node next){
            this.item=item;
            this.pre=pre;
            this.next=next;
        }
    }
    
  • 双向链表的代码实现

    public class TowWayLinkList<T> implements Iterable<T>{
        //首结点
        private Node head;   //Node此类的成员内部类
        //最后一个结点
        private Node last;
        //链表的长度
        private int N;
    ​
        public TowWayLinkList(){
            last=null;
            head = new Node(null,null,null);
            N=0;
        }
        //清空链表
        public void clear(){
            last=null;
            head.next=last;
            head.pre=null;
            head.item=null;
            N=0;
        }
        //获取链表长度
        public boolean isEmpty(){
            return N==0;
        }
        //插入元素t
        public void insert(T t){
            if (last==null){
                last = new Node(t,head,null);
                head.next = last;
            }else{
            Node oldLast = last;
            Node node = new Node(t, oldLast, null);
            oldLast.next = node;
            last = node;
            }
            //长度+1
            N++;
        }
        //向指定位置i处插入元素t
        public void insert(int i,T t){
            if (i<0 || i>N){
                throw new RuntimeException("位置不合法");
            }
            //找到位置i的前一个结点
            Node pre = head;
            for (int index = 0; index < i; index++) {
                pre = pre.next;
            }
            //当前结点
            Node curr = pre.next;
            //构建新结点
            Node newNode = new Node(t, pre, curr);
            curr.pre= newNode;
            pre.next = newNode;
            //长度+1
            N++;
        }
        //获取指定位置i处的元素
        public Object get(int i){
            if (i<0||i>N){
                throw new RuntimeException("位置不合法");
            }
            //寻找当前结点
            Node curr = head.next;
            for (int index = 0; index <i; index++) {
                curr = curr.next;
            }
            return curr.item;
        }
        //找到元素t在链表中第一次出现的位置
        public int indexOf(T t){
            Node n= head;
            for (int i=0;n.next!=null;i++){
                n = n.next;
                if (n.next.equals(t)){
                    return i;
                }
            }
            return -1;
        }
        //删除位置i处的元素,并返回该元素
        public Object remove(int i){
            if (i<0||i>N){
                throw new RuntimeException("位置不合法");
            }
            //寻找i位置的前一个元素
            Node pre = head;
            for (int index = 0; index <i ; index++) {
                pre = pre.next;
            }
            //i位置的元素
            Node curr = pre.next;
            //i位置的下一个元素
            Node curr_next = curr.next;
    ​
            pre.next = curr_next;
            curr_next.pre = pre;
            //长度-1;
            N--;
            return curr.item;
        }
        //获取第一个元素
        public Object getFirst(){
            if (isEmpty()){
                return null;
            }
            return head.next.item;
        }
        //获取最后一个元素
        public Object getLast(){
            if (isEmpty()){
                return null;
            }
            return last.item;
        }
    ​
        @Override
        public Iterator<T> iterator() {
            return  new TIterator();
        }
        private class TIterator implements Iterator{
            private Node n = head;
    ​
            @Override
            public boolean hasNext() {
                return n.next!=null;
            }
    ​
            @Override
            public Object next() {
                n = n.next;
                return n.item;
            }
        }
        //结点类
        private class Node{
            //存储元素
            public T item;
            //指向下一个结点
            public Node next;
            //指向上一个结点
            public Node pre;
            public Node(T item, Node pre, Node next) {
                this.item = item;
                this.pre = pre;
                this.next = next;
            }
        }
    }
    
  • 测试:

    public class Test {
        public static void main(String[] args) {
            TowWayLinkList<String> list = new TowWayLinkList<>();
            list.insert("乔峰");
            list.insert("虚竹");
            list.insert("段誉");
            list.insert(1,"鸠摩智");
            list.insert(3,"叶二娘");
    
            for (String str : list) {
                System.out.println(str);
            }
        }
    }
    
  • java中LinkedList实现

    • java中LinkedList集合也是使用双向链表实现,并提供了增删改查等相关方法

3.链表反转:

  • 单链表的反转,是面试中的一个高频题目

  • 反转API:

    public void reverse():对整个链表反转
    public Node reverse(Node curr):反转链表中的某个结点curr,并把反转后的curr结点返回
    

image.png

  • 单向链表反转的代码实现

    //对整个链表反转
    public void reverse(){
            if(N==0){
                    //当前是空链表,不需要反转
                    return;
            }
            reverse(head.next);
    }
    
    //反转链表中的某个结点curr,并把反转后的curr结点返回
    public Node reverse(Node curr){   //curr 当前遍历的结点
            //已经到了最后一个元素
            if (curr.next==null){
            //反转后,头结点应该指向原链表中的最后一个元素
                    head.next=curr;
                    return curr;
            }
            //当前结点的上一个结点
            Node pre = reverse(curr.next);
            pre.next = curr;
            //当前结点的下一个结点设为null
            curr.next=null;
            //返回的当前结点,成为上一个结点
            return curr;
    }
    
  • 测试:

    public class test {
        public static void main(String[] args) {
            LinkListTest<Integer> list = new LinkListTest<>();
            list.insert(1);
            list.insert(2);
            list.insert(3);
            list.insert(4);
            for (Integer i : list) {
                System.out.print(i+" ");
            }
            System.out.println();
            System.out.println("--------------------");
            list.reverse();
            for (Integer i : list) {
                System.out.print(i+" ");
            }
        }
    }
    

4.快慢指针:

  • 描述:

    • 快慢指针指的是定义两个指针,这两个指针的移动速度一快一慢,以此来制造出自己想要的差值,这个差值可以让我们找到链表上相应的结点。一般情况下,快指针的移动步长为慢指针的两倍
  • 中间值问题

image.png

image.png

  • 中间值问题的代码实现:

    public class a中间值问题 {
        public static void main(String[] args) {
            Node<String> first = new Node<String>("aa", null);
            Node<String> second = new Node<String>("bb", null);
            Node<String> third = new Node<String>("cc", null);
            Node<String> fourth = new Node<String>("dd", null);
            Node<String> fifth = new Node<String>("ee", null);
            Node<String> six = new Node<String>("ff", null);
            Node<String> seven = new Node<String>("gg", null);
    
            //完成结点之间的指向
            first.next = second;
            second.next = third;
            third.next = fourth;
            fourth.next = fifth;
            fifth.next = six;
            six.next = seven;
    
            //查找中间值
            String mid = getMid(first);
            System.out.println("中间值为:" + mid);
        }
            public static String getMid(Node<String> first) {  //first 链表的首结点
                Node<String> slow=first;   //慢指针
                Node<String> fast=first;   //快指针
                while(fast!=null && fast.next!=null){
                    slow=slow.next;
                    fast=fast.next.next;
                }
                //返回链表的中间结点的值
                return slow.item;
            }
            //结点类
            private static class Node<T> {
                //存储元素
                public T item;
                //指向下一个结点
                public Node next;
    
                public Node(T item, Node next){
                    this.item=item;
                    this.next=next;
                }
            }
    }
    

单向链表是否有环问题

  • 使用快慢指针的思想,还是把链表比作一条跑道,链表中有环,那么这条跑道就是一条圆环跑道,在一条圆环跑道中,两个人有速度差,那么迟早两个人会相遇,只要相遇那么就说明有环

image.png

image.png

    • 单向链表是否有环问题代码实现:

      public class b单向链表是否有环问题 {
          public static void main(String[] args) {
              Node<String> first = new Node<String>("aa", null);
              Node<String> second = new Node<String>("bb", null);
              Node<String> third = new Node<String>("cc", null);
              Node<String> fourth = new Node<String>("dd", null);
              Node<String> fifth = new Node<String>("ee", null);
              Node<String> six = new Node<String>("ff", null);
              Node<String> seven = new Node<String>("gg", null);
      ​
              //完成结点之间的指向
              first.next = second;
              second.next = third;
              third.next = fourth;
              fourth.next = fifth;
              fifth.next = six;
              six.next = seven;
      ​
              //产生环
              seven.next = third;
      ​
              //判断链表是否有环,ture为有环,false为无环
              boolean circle = isCircle(first);
              System.out.println("first链表中是否有环:"+circle);
          }
      ​
          public static boolean isCircle(Node<String> first) {   // first 链表首结点
              //定义快慢指针
              Node<String> slow=first;
              Node<String> fast=first;
              //遍历链表,如果快慢指针指向了同一结点,那么证明有环
              while(fast!=null&&fast.next!=null){
                  slow=slow.next;
                  fast=fast.next.next;
                  if(fast.equals(slow)){
                      return true;
                  }
              }
              return false;
          }
      ​
          //结点类
          private static class Node<T> {
              //存储元素
              public T item;
              //指向下一个结点
              public Node next;
      ​
              public Node(T item, Node next){
                  this.item=item;
                  this.next=next;
              }
          }
      }
      
  • 有环链表入口问题

    • 当快慢指针相遇时,我们可以判断到链表中有环,这时重新设定* *一个新指针指向链表的起点,且步长与慢指针一样为1,则慢指针与“新”指针相遇的地方就是环的入口。证明这一结论牵涉到数论的知识,这里略,只讲实现

image.png

image.png

    • 有环链表入口问题代码实现:

      public class c有环链表入口问题 {
          public static void main(String[] args) {
              Node<String> first = new Node<String>("aa", null);
              Node<String> second = new Node<String>("bb", null);
              Node<String> third = new Node<String>("cc", null);
              Node<String> fourth = new Node<String>("dd", null);
              Node<String> fifth = new Node<String>("ee", null);
              Node<String> six = new Node<String>("ff", null);
              Node<String> seven = new Node<String>("gg", null);
      ​
              //完成结点之间的指向
              first.next = second;
              second.next = third;
              third.next = fourth;
              fourth.next = fifth;
              fifth.next = six;
              six.next = seven;
      ​
              //产生环
              seven.next = third;
      ​
              //查找环的入口结点
              Node<String> entrance = getEntrance(first);
              System.out.println("first链表中环的入口结点元素为:"+entrance.item);
          }
      ​
          public static Node getEntrance(Node<String> first) {   // first 链表首结点
              //定义快慢指针,新指针
              Node<String> slow=first;
              Node<String> fast=first;
              Node<String> temp=null;
              while(fast!=null&&fast.next!=null){
                  slow=slow.next;
                  fast=fast.next.next;
                  if(fast.equals(slow)){   //快慢指针相遇时,新指针指向首结点,不往下走
                      temp=first;
                      continue;
                  }
                  if(temp!=null){
                      temp=temp.next;
                      //判断新指针是否和慢指针相遇
                      if(temp.equals(slow)){
                          return temp;   // 返回环的入口结点
                      }
                  }
              }
              return null;
          }
          //结点类
          private static class Node<T> {
              //存储元素
              public T item;
              //指向下一个结点
              public Node next;
      ​
              public Node(T item, Node next){
                  this.item=item;
                  this.next=next;
              }
          }
      }
      

5.循环链表:

  • 循环链表,顾名思义,链表整体要形成一个圆环状。在单向链表中,最后一个节点的指针为null,不指向任何结点,因为没有下一个元素了。要实现循环链表,我们只需要让单向链表的最后一个节点的指针指向头结点即可

image.png

  • 循环链表的构建:

    public class a循环链表的构建 {
        public static void main(String[] args) {
            //构建结点
            Node<Integer> first = new Node<Integer>(1, null);
            Node<Integer> second = new Node<Integer>(2, null);
            Node<Integer> third = new Node<Integer>(3, null);
            Node<Integer> fourth = new Node<Integer>(4, null);
            Node<Integer> fifth = new Node<Integer>(5, null);
            Node<Integer> six = new Node<Integer>(6, null);
            Node<Integer> seven = new Node<Integer>(7, null);
    ​
            //构建单链表
            first.next = second;
            second.next = third;
            third.next = fourth;
            fourth.next = fifth;
            fifth.next = six;
            six.next = seven;
    ​
            //构建循环链表,让最后一个结点指向第一个结点
            seven.next = first;
        }
        //结点类
        private static class Node<T> {
            //存储元素
            public T item;
            //指向下一个结点
            public Node next;
    ​
            public Node(T item, Node next){
                this.item=item;
                this.next=next;
            }
        }
    }
    

6.约瑟夫问题:

  • 问题描述:

    • 传说有这样一个故事,在罗马人占领乔塔帕特后,39 个犹太人与约瑟夫及他的朋友躲到一个洞中,39个犹太人决定宁愿死也不要被敌人抓到,于是决定了一个自杀方式,41个人排成一个圆圈,第一个人从1开始报数,依次往后,如果有人报数到3,那么这个人就必须自杀,然后再由他的下一个人重新从1开始报数,直到所有人都自杀身亡为止。然而约瑟夫和他的朋友并不想遵从。于是,约瑟夫要他的朋友先假装遵从,他将朋友与自己安排在第16个与第31个位置,从而逃过了这场死亡游戏 。
  • 问题转换:

    • 41个人坐一圈,第一个人编号为1,第二个人编号为2,第n个人编号为n。

      1.编号为1的人开始从1报数,依次向后,报数为3的那个人退出圈;

      2.自退出那个人开始的下一个人再次从1开始报数,以此类推;

      3.求出最后退出的那个人的编号。

  • 图示:

image.png

  • 解题思路:

    1.构建含有41个结点的单向循环链表,分别存储1~41的值,分别代表这41个人;

    2.使用计数器count,记录当前报数的值;

    3.遍历链表,每循环一次,count++;

    4.判断count的值,如果是3,则从链表中删除这个结点并打印结点的值,把count重置为0;

  • 约瑟夫问题代码实现:

    public class JosephTest {
        public static void main(String[] args) {
            //1.构建循环链表,包含41个结点,分别存储1-41之间的值
            //用来表示首结点
            Node<Integer> first=null;
            //用来记录前一个结点
            Node<Integer> pre=null;
            for(int i=1;i<=41;i++){
                //如果是第一个结点
                if(i==1){
                    first=new Node<>(i,null);
                    pre=first;
                }
                //如果不是第一个结点
                Node<Integer> newNode=new Node<>(i,null);
                pre.next=newNode;
                pre=newNode;
                //如果是最后一个结点,那么需要让最后一个结点的下一个结点变成first,变为循环链表了
                if(i==41){
                    pre.next=first;
                }
            }
            //2.需要count计数器,模拟报数
            int count=0;
            //3.遍历循环链表
            //记录每次遍历拿到的结点,默认从首结点开始
            Node<Integer> n=first;
            //记录当前结点的上一个结点
            Node<Integer> before=null;
            while(n!=n.next){  //n=n.next是只剩一个人的情况
                //判断count的值,如果是3,则从链表中删除这个结点并打印结点的值,把count重置为0;
                count++;
                if(count==3){
                    //删除当前结点,让上一个结点指向n的下一个结点
                    before.next = n.next;
                    System.out.print(n.item+",");
                    count=0;
                    n = n.next;
                }else{
                    before=n;
                    n = n.next;  
                }
            }
            /*打印剩余的最后那个人*/
            System.out.println(n.item);
        }
        //结点类
        private static class Node<T> {
            //存储元素
            public T item;
            //指向下一个结点
            public Node next;
    
            public Node(T item, Node next){
                this.item=item;
                this.next=next;
            }
        }
    }
    

1.二叉数入门:

1.数的基本定义:

  • 树是我们计算机中非常重要的一种数据结构,同时使用树这种数据结构,可以描述现实生活中的很多事物,例如家

    谱、单位的组织架构、等等。

    树是由n(n>=1)个有限结点组成一个具有层次关系的集合。把它叫做“树”是因为它看起来像一棵倒挂的树,也就

    是说它是根朝上,而叶朝下的

image.png

  • 树具有以下特点:

    1.每个结点有零个或多个子结点;

    2.没有父结点的结点为根结点;

    3.每一个非根结点只有一个父结点;

    4.每个结点及其后代结点整体上可以看做是一棵树,称为当前结点的父结点的一个子树;

2.数的相关术语:

  • 结点的度

    一个结点含有的子树的个数称为该结点的度

  • 叶结点:

    度为0的结点称为叶结点,也可以叫做终端结点

  • 分支结点:

    度不为0的结点称为分支结点,也可以叫做非终端结点

  • 结点的层次:

    从根结点开始,根结点的层次为1,根的直接后继层次为2,以此类推

  • 结点的层序编号:

    将树中的结点,按照从上层到下层,同层从左到右的次序排成一个线性序列,把他们编成连续的自然数。

  • 树的度:

    树中所有结点的度的最大值

  • 树的高度(深度):

    树中结点的最大层次

  • 森林:

    m(m>=0)个互不相交的树的集合,将一颗非空树的根结点删去,树就变成一个森林;给森林增加一个统一的根结点,森林就变成一棵树

image.png

  • 孩子结点:

    一个结点的直接后继结点称为该结点的孩子结点

  • 双亲结点(父结点):

    一个结点的直接前驱称为该结点的双亲结点

  • 兄弟结点:

    同一双亲结点的孩子结点间互称兄弟结点

3.二叉数的基本定义:

  • 二叉树就是度不超过2的树(每个结点最多有两个子结点)

image.png

  • 满二叉树:

    一个二叉树,如果每一个层的结点树都达到最大值,则这个二叉树就是满二叉树

image.png

  • 完全二叉树:

叶节点只能出现在最下层和次下层,并且最下面一层的结点都集中在该层最左边的若干位置的二叉树

image.png

4.二叉查找树的创建:

1.结点类:

类名Node<Key,Value>
构造方法Node(Key key, Value value, Node left, Node right):创建Node对象
成员变量1.public Node left:记录左子结点 2.public Node right:记录右子结点 3.public Key key:存储键 4.public Value value:存储值
  • 代码实现:

    public class TreeNode<Key,Value> {
        //存储键
        public Key key;
        //存储值
        private Value value;
        //记录左子结点
        public TreeNode left;
        //记录右子结点
        public TreeNode right;
    
        public TreeNode(Key key, Value value, TreeNode left, TreeNode right) {
            this.key = key;
            this.value = value;
            this.left = left;
            this.right = right;
        }
    }
    

2.API设计:

类名BinaryTree<,Value value>
构造方法BinaryTree():创建BinaryTree对象
成员变量1.private Node root:记录根结点 2.private int N:记录树中元素的个数
成员方法1. public void put(Key key,Value value):向树中插入一个键值对 2.private Node put(Node x, Key key, Value val):给指定树x上,添加键一个键值对,并返回添 加后的新树 3.public Value get(Key key):根据key,从树中找出对应的值 4.private Value get(Node x, Key key):从指定的树x中,找出key对应的值 5.public void delete(Key key):根据key,删除树中对应的键值对 6.private Node delete(Node x, Key key):删除指定树x上的键为key的键值对,并返回删除后的 7.public int size():获取树中元素的个数

3.实现:

4.其他便捷方法:

5.二叉树的基础遍历:

  • 前序遍历:先访问根结点,然后再访问左子树,最后访问右子树
  • 中序遍历:先访问左子树,中间访问根节点,最后访问右子树
  • 后序遍历:先访问左子树,再访问右子树,最后访问根节点
  • 图示:

image.png

1.前序遍历:

  • 实现过程中,我们通过前序遍历,把,把每个结点的键取出,放入到队列中返回即可

  • 实现步骤:

    1.把当前结点的key放入到队列中;

    2.找到当前结点的左子树,如果不为空,递归遍历左子树

    3.找到当前结点的右子树,如果不为空,递归遍历右子树

  • 代码实现:

    //使用前序遍历,获取整个树中的所有键 
    public Queue<Key> preErgodic(){ 
        Queue<Key> keys = new Queue<>(); 
        preErgodic(root,keys); 
        return keys; 
    }
    //使用前序遍历,把指定树x中的所有键放入到keys队列中
    private void preErgodic(Node x,Queue<Key> keys){
        if (x==null){ 
            return; 
        }
        //1.把当前结点的key放入到队列中; 
        keys.enqueue(x.key); 
        //2.找到当前结点的左子树,如果不为空,递归遍历左子树 
        if (x.left!=null){ 
            preErgodic(x.left,keys); 
        }
        //3.找到当前结点的右子树,如果不为空,递归遍历右子树 
        if (x.right!=null){ 
            preErgodic(x.right,keys); 
        }
    }
    //测试代码 
    public class Test { 
        public static void main(String[] args) throws Exception { 
            BinaryTree<String, String> bt = new BinaryTree<>(); 
            bt.put("E", "5"); 
            bt.put("B", "2"); 
            bt.put("G", "7");
            bt.put("A", "1"); 
            bt.put("D", "4"); 
            bt.put("F", "6"); 
            bt.put("H", "8"); 
            bt.put("C", "3"); 
            Queue<String> queue = bt.preErgodic(); 
            for (String key : queue) {
                System.out.println(key+"="+bt.get(key)); 
            }
        } 
    }
    

2.中序遍历:

  • 实现步骤:

    1.找到当前结点的左子树,如果不为空,递归遍历左子树

    2.把当前结点的key放入到队列中;

    3.找到当前结点的右子树,如果不为空,递归遍历右子树

  • 代码实现:

    //使用中序遍历,获取整个树中的所有键 
    public Queue<Key> midErgodic(){ 
        Queue<Key> keys = new Queue<>(); 
        midErgodic(root,keys);
        return keys;
    }
    //使用中序遍历,把指定树x中的所有键放入到keys队列中 
    private void midErgodic(Node x,Queue<Key> keys){ 
        if (x==null){ 
            return; 
        }
        //1.找到当前结点的左子树,如果不为空,递归遍历左子树 
        if (x.left!=null){
            midErgodic(x.left,keys); 
        }
        //2.把当前结点的key放入到队列中; 
        keys.enqueue(x.key); 
        //3.找到当前结点的右子树,如果不为空,递归遍历右子树 
        if (x.right!=null){ 
            midErgodic(x.right,keys); 
        } 
    }
    //测试代码 
    public class Test { 
        public static void main(String[] args) throws Exception { 
            BinaryTree<String, String> bt = new BinaryTree<>(); 
            bt.put("E", "5");
            bt.put("B", "2"); 
            bt.put("G", "7"); 
            bt.put("A", "1"); 
            bt.put("D", "4"); 
            bt.put("F", "6"); 
            bt.put("H", "8"); 
            bt.put("C", "3"); 
            Queue<String> queue = bt.midErgodic(); 
            for (String key : queue) {
                System.out.println(key+"="+bt.get(key)); 
            }
        }
    }
    

3.后序遍历:

  • 实现步骤:

    1.找到当前结点的左子树,如果不为空,递归遍历左子树

    2.找到当前结点的右子树,如果不为空,递归遍历右子树

    3.把当前结点的key放入到队列中;

  • 代码实现:

    //使用后序遍历,获取整个树中的所有键 
    public Queue<Key> afterErgodic(){ 
        Queue<Key> keys = new Queue<>(); 
        afterErgodic(root,keys);
        return keys; 
    }
    //使用后序遍历,把指定树x中的所有键放入到keys队列中 
    private void afterErgodic(Node x,Queue<Key> keys){
        if (x==null){
            return; 
        }
        //1.找到当前结点的左子树,如果不为空,递归遍历左子树 
        if (x.left!=null){
            afterErgodic(x.left,keys); 
        }
        //2.找到当前结点的右子树,如果不为空,递归遍历右子树 
        if (x.right!=null){ 
            afterErgodic(x.right,keys);
        }
        //3.把当前结点的key放入到队列中;
        keys.enqueue(x.key); 
    }
    //测试代码 
    public class Test { 
        public static void main(String[] args) throws Exception { 
            BinaryTree<String, String> bt = new BinaryTree<>();
            bt.put("E", "5");
            bt.put("B", "2"); 
            bt.put("G", "7");
            bt.put("A", "1");
            bt.put("D", "4"); 
            bt.put("F", "6");
            bt.put("H", "8");
            bt.put("C", "3"); 
            Queue<String> queue = bt.afterErgodic(); 
            for (String key : queue) { 
                System.out.println(key+"="+bt.get(key));
            }
        }
    }
    

6.二叉树的层序遍历:

  • 所谓的层序遍历,就是从根节点(第一层)开始,依次向下,获取每一层所有结点的值

image.png

  • 那么层序遍历的结果是:EBGADFHC

  • 实现步骤:

    1.创建队列,存储每一层的结点;

    2.使用循环从队列中弹出一个结点:

    2.1获取当前结点的key;

    2.2如果当前结点的左子结点不为空,则把左子结点放入到队列中

    2.3如果当前结点的右子结点不为空,则把右子结点放入到队列中

image.png

image.png

    • 代码实现:

      //使用层序遍历得到树中所有的键 
      public Queue<Key> layerErgodic(){
          Queue<Key> keys = new Queue<>();
          Queue<Node> nodes = new Queue<>(); 
          nodes.enqueue(root); 
          while(!nodes.isEmpty()){
              Node x = nodes.dequeue();
              keys.enqueue(x.key); 
              if (x.left!=null){ 
                  nodes.enqueue(x.left); 
              }
              if (x.right!=null){ 
                  nodes.enqueue(x.right); 
              } 
          }
          return keys;
      }
      //测试代码 
      public class Test { 
          public static void main(String[] args) throws Exception { 
              BinaryTree<String, String> bt = new BinaryTree<>(); 
              bt.put("E", "5"); 
              bt.put("B", "2");
              bt.put("G", "7");
              bt.put("A", "1"); 
              bt.put("D", "4"); 
              bt.put("F", "6"); 
              bt.put("H", "8"); 
              bt.put("C", "3"); 
              Queue<String> queue = bt.layerErgodic(); 
              for (String key : queue) {
                  System.out.println(key+"="+bt.get(key));
              }
          } 
      }
      

7.二叉树的最大深度问题:

  • 实现步骤:

    1.如果根结点为空,则最大深度为0;

    2.计算左子树的最大深度;

    3.计算右子树的最大深度;

    4.当前树的最大深度=左子树的最大深度和右子树的最大深度中的较大者+1

  • 代码实现:

    //计算整个树的最大深度 
    public int maxDepth() { 
        return maxDepth(root); 
    }
    //计算指定树x的最大深度 
    private int maxDepth(Node x) { 
        //1.如果根结点为空,则最大深度为0; 
        if (x == null) { 
            return 0; 
        }
        int max = 0; 
        int maxL = 0; 
        int maxR = 0; 
        //2.计算左子树的最大深度; 
        if (x.left != null) {
            maxL = maxDepth(x.left); 
        }
        //3.计算右子树的最大深度; 
        if (x.right != null) { 
            maxR = maxDepth(x.right); 
        }
        //4.当前树的最大深度=左子树的最大深度和右子树的最大深度中的较大者+1 
        max = maxL > maxR ? maxL + 1 : maxR + 1; 
        return max; 
    }
    

8.折纸问题:

2.堆:

1.堆的定义:

  • 堆是计算机科学中一类特殊的数据结构的统称,堆通常可以被看做是一棵完全二叉树的数组对象

  • 堆的特性

    • 它是完全二叉树

image.png

    • 它通常用数组来实现:将二叉树的结点按照层级顺序放入数组中,根结点在位置1,它的子结点在位置2和34

      父节点的左子节点2n,左子节点2n+1;如果一个结点的位置为k,则它的父结点的位置为 [k/2]

    • 每个结点都大于等于它的两个子结点

2.堆的API设计:

类名Heap
构造方法Heap(int capacity):创建容量为capacity的Heap对象
成员方法1.private boolean less(int i,int j):判断堆中索引i处的元素是否小于索引j处的元素 2.private void exch(int i,int j):交换堆中i索引和j索引处的值 3.public T delMax():删除堆中最大的元素,并返回这个最大元素 4.public void insert(T t):往堆中插入一个元素 5.private void swim(int k):使用上浮算法,使索引k处的元素能在堆中处于一个正确的位置 6.private void sink(int k):使用下沉算法,使索引k处的元素能在堆中处于一个正确的位置
成员变量1.private T[] imtes : 用来存储元素的数组 2.private int N:记录堆中元素的个数

3.堆的实现:

  • 上浮算法:(插入元素)

    • 每次插入一个元素,都会使得堆中的数据顺序变乱,这个时候我们就需要通过一些方法让刚才插入的这个数据放入到合适的位置

image.png

image.png

image.png

image.png 下沉算法: (删除元素)

  • 由堆的特性我们可以知道,索引1处的元素,也就是根结点就是最大的元素,当我们把根结点的元素删除后,需要有一个新的根结点出现,这时我们可以暂时把堆中最后一个元素放到索引1处,充当根结点,但是它有可能不满足堆的有序性需求,这个时候我们就需要通过一些方法,让这个新的根结点放入到合适的位置。

image.png

image.png

image.png

image.png

image.png

image.png

    • 代码实现:

      public class Heap<T extends Comparable<T>> {
          //存储堆中的元素
          private T[] items;
          //记录堆中元素的个数
          private int N;
      ​
          public Heap(int capacity){
              items=(T[]) new Comparable[capacity+1];
              N=0;
          }
      ​
          //判断堆中索引i处的元素是否小于索引j处的元素
          private boolean less(int i,int j){
              return items[i].compareTo(items[j])<0;
          }
          //交换堆中i索引和j索引处的值
          private void exch(int i,int j){
              T tmp = items[i];
              items[i] = items[j];
              items[j] = tmp;
          }
      ​
          //往堆中插入一个元素
          public void insert(T t){
              items[++N] = t;    //++N可以废弃下标0
              swim(N);
          }
          //删除堆中最大的元素,并返回这个最大元素
          public T delMax(){
              T max = items[1];
              //交换索引1处和索引N处的值
              exch(1,N);
              //删除最后位置上的元素
              items[N]=null;
              N--;
              //个数-1
              sink(1);
              return max;
          }
          //使用上浮算法,使索引k处的元素能在堆中处于一个正确的位置
          private void swim(int k){
              //如果已经到了根结点,就不需要循环了
              while(k>1){
                  //比较其父结点和当前结点
                  if(less(k/2,k)){
                      //父结点小于当前结点,需要交换
                      exch(k/2,k);
                  }
                  k = k/2;
              }
          }
          //使用下沉算法,使索引k处的元素能在堆中处于一个正确的位置
          private void sink(int k) {
              //如果当前已经是最底层了,就不需要循环了
              while (2 * k <= N) {
                  //找到子结点中的较大者
                  int max;
                  if (2 * k + 1 <= N) {
                      //存在右子结点
                      if (less(2 * k, 2 * k + 1)) {
                          max = 2 * k + 1;
                      } else {
                          max = 2 * k;
                      }
                  } else {
                      //不存在右子结点
                      max = 2 * k;
                  }
                  //比较当前结点和子结点中的较大者,如果当前结点不小,则结束循环
                  if (!less(k, max)) {
                      break;
                  }
                  //当前结点小,则交换,
                  exch(k, max);
                  k = max;
              }
          }
      }
      

4.堆排序:

  • 堆的构建:

    • 从小到大排序就构建大根堆,从大到小排序就构建小跟堆
  • 堆的排序:

    • 利用堆的构建;如小到大排序,最大元素位于根结点,然后将根结点和尾结点交换

      1.将堆顶元素和堆中最后一个元素交换位置;

      2.通过对堆顶元素下沉调整堆,把最大的元素放到堆顶(此时最后一个元素不参与堆的调整,因为最大的数据已经到了数组的最右边)

  • 代码实现:

    public class 从小到大 {
        //对source数组中的数据从小到大排序
        public static void sort(int[] source) {
            //1.创建一个比原数组大1的数组
            int[] heap = new int[source.length + 1];
            //2.构造堆
            createHeap(source,heap);
            //3.堆排序
            //定义一个变量,记录heap中未排序的所有元素中最大的索引
            int N = heap.length-1;
            while(N!=1){
                exch(heap,1,N);
                N--;
                sink(heap,1,N);
            }
    
            //4.heap中的数据已经有序,拷贝到source中
            System.arraycopy(heap,1,source,0,source.length);
        }
        //根据原数组source,构造出堆heap
        private static void createHeap(int[] source, int[] heap) {   //source原数组  //heap待排序数组
            //复制数组
            System.arraycopy(source,0,heap,1,source.length);
            //对堆中的元素做下沉调整(从数组长度一半开始,往索引1处扫描)
            for(int i= heap.length/2;i>0;i--){
                sink(heap,i, heap.length-1);
            }
        }
        //交换heap堆中i索引和j索引处的值
        private static void exch(int[] heap, int i, int j) {
            int temp=heap[i];
            heap[i]=heap[j];
            heap[j]=temp;
        }
        //判断heap堆中索引i处的元素是否小于索引j处的元素
        private static boolean less(int[] heap, int i, int j) {
            return heap[i]-heap[j]<0;    //i是否小于j
        }
        //在heap堆中,对target处的元素做下沉,范围是0~range
        private static void sink(int[] heap, int target, int range){  //range范围
            while(target*2<=range){
                int max=target*2;
                //存在右结点的情况
                if(target*2+1<=range){
                    if(less(heap,target*2,target*2+1)){
                        max=target*2+1;
                    }
                }
                //两种情况:右结点比左结点大,所以max=右结点;左结点大,max=左结点
                if(less(heap,target,max)){
                    exch(heap,target,max);
                }
                target=max;
            }
        }
    
        public static void main(String[] args) {
            int[] arr={4,8,9,1,6,2,3,4};
            sort(arr);
            System.out.println(Arrays.toString(arr));
        }
    }