数据结构和算法

算法概述:

                算法=输入+程序+输出

                        针对某个特点问题的解决方案

                                先有解决问题的主体思路

                                功能分解,逐步求精

                        算法也有好坏之分,主要使用O进行标识

                                    时间复杂度:算法执行所需要的时间

                                    空间复杂度:算法执行所需要的空间

        基础算法:

                        排序:

                                简单排序:冒泡,插入,选择

                                高级排序:希尔,归并,堆,计数,快速

                        查询:

                                二分查找

        数据结构概述:

                        就是数据在内存中储存的方式

                                在内存中物理储存方式,

                                     数组

                                     链表

                                逻辑上的储存方式

                                     线性结构

                                        线性表

                                         栈

                                        队列

                                    数结构

                                         二叉树

                                         查询树

                                         红黑树

                                         平衡树

                                         B树

                                     哈希表

                                     图

        排序算法:

                        三种简单排序的区别,时间复杂度都是0(n^2)

                                冒泡:

                                        两个相邻的比较,一步步把最大的数字换到数组最后,知道排序完;

                                         遍历次数多,交换次数也多

                                 选择                

                                       从未排序的集合中选择最大或者最小值,放到已经排序好的集合中

                                        遍历次数多,但交换次数少

                                 插入

                                        从未排序的集合中拿一个数字,从已经排序的集合中找到插入位置,进行插入

                                         遍历次数少,交换次数多

        高级排序(算法的速度都比简单排序快)

                        快速排序

                        希尔排序

                        归并排序

                        堆排序

                        计数排序

    查询算法

                传统的查询算法需要遍历整个集合,时间复杂度O(n)

                使用二分查找之后,时间复杂度O(logN)

                二分查找只能针对有序集合


    public static void binarySeach(int[] arrs,int target,int start,int end){
        //1,获取数组的中间值下标
        int middle = (start+end)/2;
        //2,将中间值和目标进行比较
        if(target == arrs[middle]){
            System.out.println("找到目标,下标是:"+middle);
        }else if(target > arrs[middle]){
            //从右边开始找
            binarySeach(arrs,target,middle+1,end);
        }else{
            //从左边开始找
            binarySeach(arrs,target,start,middle-1);
        }
    }

             添加递归退出条件

 if(end<start){//递归退出条件
            //没有找到目标
            System.out.println("没有找到目标");
            return;
     }

             带返回结果的二分查找

     

public static Integer binarySeach(int[] arrs,int target,int start,int end){
        if(end<start){//递归退出条件
            //没有找到目标
            //System.out.println("没有找到目标");
            return null;
        }
        //1,获取数组的中间值下标
        int middle = (start+end)/2;
        //2,将中间值和目标进行比较
        if(target == arrs[middle]){
            //System.out.println("找到目标,下标是:"+middle);
            return middle;
        }else if(target > arrs[middle]){
            //从右边开始找
            return binarySeach(arrs,target,middle+1,end);
        }else{
            //从左边开始找
            return binarySeach(arrs,target,start,middle-1);
        }
    }

       两种物理结构

                      数组

                                是内存中连续的空间,在创建的时候大小就确定了

                                通过下标可以直接获取数组里面的元素

                                会有扩容问题 

                        链表

                                在内存中不是连续的空间,大小可以不固定

                                可以通过下标找元素,但需要一个个查询

                                新增和删除速度快,没有扩容问题,但是查询速度慢

                链表的实现

                        创建一个节点类

    //给链表设计一个节点
    public class Node<T>{
        T data;//存储的数据
        Node next;//指向下一个节点的引用

        public Node(T data, Node next) {
            this.data = data;
            this.next = next;
        }
        public T getData() {
            return data;
        }
        public void setData(T data) {
            this.data = data;
        }
    }

实现新增和查询方法

    //新增方法
    public void add(T t){
        if(head==null){
            head = new Node(t,null);
        }else{
            Node temp = head;
            //找到最后一个节点
            while(temp.next!=null){
                temp = temp.next;
            }
            //将新增的节点放入到最后一个节点的next引用
            temp.next = new Node(t,null);
        }
        size++;
    }
    //查询方法
    public T get(int pos){
        if(pos>=size){
            throw new IndexOutOfBoundsException();
        }

        int cnt = 0;
        Node<T> temp = head;//从头部开始
        //向下移动
        while(cnt != pos){
            temp = temp.next;
            cnt++;
        }
        return temp.getData();
    }

指定位置的新增

   public void add(int pos, T t) {
        //创建新的节点
        Node newNode = new Node(t, null);
        if(pos == 0){//在头部插入
            //先将新的节点的next指向头部
            newNode.next = head;
            head = newNode;
        }else{
            //当前位置的
            Node current = getNode(pos-1);
            //将新节点的下一个位置,执行当前节点的下一个位置
            newNode.next = current.next;
            //将当前位置的节点指向新增的节点
            current.next = newNode;
        }
        size++;
    }

指定位置的删除

    //删除指定位置的数据
    public void remove(int i) {
        if(i==0){
            //将头节点直接移动到下一个节点
            head = head.next;
        }else{
            //找到节点
            Node current  =    getNode(i);
            Node preNode = getNode(i-1);
            //前面的节点,直接指向下后面的节点
            preNode.next = current.next;
        }
        //删除
        size--;
    }

栈和队列:

                栈:先进后出

                自定义实现栈

public class MyStack<T> {

    MyLinkedList<T> datas = new MyLinkedList<>();

    //压入栈
    public void push(T t){
        //放在链表的尾部
        datas.add(t);
    }
    //出栈
    public T pop(){
        //获取头部
        T t = datas.get(datas.size-1);
        datas.remove(datas.size-1);
        return t;
    }
}

JDK自带的Stack类

Stack<String> myStack = new Stack<>();

        myStack.push("aa");
        myStack.push("bb");
        myStack.push("cc");

        System.out.println(myStack.pop());
        System.out.println(myStack.pop());

队列:先进先出

        自定义实现

        MyQueue<String> queue = new MyQueue<>();
        queue.add("aa");
        queue.add("bb");
        queue.add("cc");

        System.out.println(queue.poll());
        System.out.println(queue.poll());

JDK自带的实现

 //数组实现
        Queue<String> queue = new ArrayDeque<>();
        //链表实现
        queue = new LinkedList<>();

树结构:

        是一种非线性结构

                既有数组查询的效率,又有链表新增和删除的性能

        树的基本特点

                每棵树有且仅有一个根节点没有父节点

                除了根节点,所有节点都只有一个父节点 

                每个节点都可以有多个子节点,或者没有子节点

                最多只有两个子节点的树叫二叉树

链表实现树

        实现树节点

   public class Node<T>{
        T data;//数据
        Node<T> left;//左子节点
        Node<T> right;//右子节点
   }

创建一颗树

    //树的根节点
    Node root;

    //创建一颗树
    public void createTree(){
        //创建根节点
        root = new Node(1);
        //左子节点
        Node left = new Node(2);
        root.setLeft(left);
        //右边子节点
        Node right= new Node(3);
        root.setRight(right);

        //左子左节点
        Node leftLeft = new Node(4);
        left.setLeft(leftLeft);
        //左子右节点
        Node leftRight = new Node(5);
        left.setRight(leftRight);

        //右子左节点
        Node rightLeft = new Node(6);
        right.setLeft(rightLeft);
        //右子右节点
        Node rightRight = new Node(7);
        right.setRight(rightRight);
    }

树的遍历

        先序遍历:根节点->左节点->右节点

  //实现先序遍历
    public void preTrival(Node node){
        if(node ==null){
            return;
        }
        //直接输出根节点
        System.out.print(node.data+",");
        //遍历左子节点
        preTrival(node.left);
        //遍历右子节点
        preTrival(node.right);
    }

中序遍历:左节点->根节点->右节点

    //实现中序遍历
    public void midTrival(Node node){
        if(node==null){
            return;
        }
        //遍历左子节点
        midTrival(node.left);
        //打印根节点
        System.out.print(node.data+",");
        //遍历右子节点
        midTrival(node.right);
    }

后序遍历:左节点->右节点->根节点

   //实现后序遍历
    public void postTrival(Node node){
        if(node==null){
            return;
        }
        //遍历左子节点
        postTrival(node.left);
        //遍历右子节点
        postTrival(node.right);
        //打印根节点
        System.out.print(node.data+",");
    }

数组实现树

        基本规则

                使用数组储存

                只能构造完全二叉树

                左子节点=当前节点下标*2+1

                右子节点=当前节点下标*2+2

遍历

    //实现先序遍历
    public void preTrival(int index){
        if(index>= datas.length){
            return;
        }
        //打印根节点
        System.out.print(datas[index]+",");
        //遍历左子节点
        preTrival(2*index+1);
        //遍历右子节点
        preTrival(2*index+2);
    }

    //实现先序遍历
    public void midTrival(int index){
        if(index>= datas.length){
            return;
        }
        //遍历左子节点
        midTrival(2*index+1);
        //打印根节点
        System.out.print(datas[index]+",");
        //遍历右子节点
        midTrival(2*index+2);
    }

堆排序:

        使用数组树

        构造一个大顶堆(所有的父类节点要比子节点要打,最大的值就会在根节点)

        在构造完大顶堆之后,将根节点和最后一个叶子节点交换,并且脱离树结构

        再将树结构继续构造大顶堆,然后再和最后一个叶子节点交换,直到排序完毕

查询树:

        树既有链表新增和删除的优势,又有数组查询的效率

        一般的树是不具备快速查询的能力,二叉树天然支持二分查找,可以实现快速查找

查询树特点:

        左子节点要比右子节点小

        在构建查询树的时候有可能会出现左右失去平衡的问题

二叉平衡树

         是一个特殊的查询树

        在构造树的时候会通过旋转算法使树平衡

        当左子树与右子树的高度差大于1的时候,树就会失去平衡,要通过旋转算法来平衡

        由于平衡树失去平衡的限制太高了,会导致树在旋转的次数增加,降低效率

红黑树(RBTree)

        也是一颗特殊的查询树

        主要将树节点分为红黑两种颜色

        任意节点到每个叶子节点的黑色节点的数量一致,如果不一致会失去平衡进行旋转

       无论树的层数多高,每次旋转都不会超过三次 

多路查询树:

        二叉查询树的问题,在于数据多的时候,树的高度会很高,从而影响性能
        多路查找树,不像二叉树之后两子节点,它具备多个子节点,可以有效降低树的高度提高查询速度

        多路查找树就是B树,B+树就是在B树的基础上,将所有的叶子节点通过链表链起来

霍夫曼树

        是带权重的树

        主要用压缩算法

哈希表

        哈希表查询速度非常快,可以达到O(1)

        主要原理

                创建一个Entry用于储存数据的Key和Value值

                主要通过hash散列算法,算出要插入的元素位置

                然后根据计算出的问题,见Entry插入到对应数组中

  基本实现:

public class MyHashTable<K,V> {
    Entry<K,V>[] elements ;//用于存储Entry的数组
    public MyHashTable(){
        //创建Entry的数组
        elements = new Entry[10];
    }
    //计算所在的位置,散列算法
    public int hash(K k){
           return k.hashCode()%elements.length;
    }

    //插入
    public void put(K k ,V v){
        //使用K执行相关散列算法,获取存储在位置
        int pos = hash(k);
        //创建Entry放入到指定的位置
        elements[pos] = new Entry<>(k,v);
    }

    //获取
    public V get(K k){
        //通过散列算法获取位置
        int pos = hash(k);
        //通过位置获取Entry
        Entry<K,V> entry = elements[pos];
        return entry.getValue();
    }

    //定义一个Entry用于存储key和value
    public static class Entry<K,V>{
        K key;
        V value;

        public Entry(K key, V value) {
            this.key = key;
            this.value = value;
        }

        public K getKey() {
            return key;
        }

        public void setKey(K key) {
            this.key = key;
        }

        public V getValue() {
            return value;
        }

        public void setValue(V value) {
            this.value = value;
        }
    }
}

        散列冲突问题

                由于数组长度是有限的,在计算entry插入的位置的时候,必然会出现冲突问题

        解决方法1:开放寻址法:

                在发生冲突的时候通过重新计算,或者扩容的方式,来解决冲突问题

                好处可以保证Entry实在数组中,可以序列化

                缺点:会浪费很多空间,在重新计算的时候会大大影响效率

                适合数据量不大的数据

解决方式:链表式

                在发生冲突的时候,在冲突的地方通过链表添加新的数据

                好处是插入的时候不需要考虑数组扩容问题

                缺点是链表逐渐扩大,会导致性能问题

                将entry转变成链表

 public static class Entry<K,V>{
        K key;
        V value;
        Entry<K,V> next;//指向下个节点的引用
   }

                修改插入和查询

    

    //插入
    public void put(K k ,V v){
        //使用K执行相关散列算法,获取存储在位置
        int pos = hash(k);

        //判断是否发生碰撞
        if(elements[pos]==null){
            //创建Entry放入到指定的位置
            elements[pos] = new Entry<>(k,v);
        }else{
            Entry temp = elements[pos];
            //链表的遍历
            while(temp.next!=null){
                temp = temp.next;
            }
            //插入到链表下一个为null的位置
            temp.next =  new Entry<>(k,v);
        }


    }

    //获取
    public V get(K k){
        //通过散列算法获取位置
        int pos = hash(k);
        //通过位置获取Entry
        Entry<K,V> entry = elements[pos];
        //判断Entry的key的值是否一致
        if(entry.getKey().equals(k)){
            return entry.getValue();
        }else{
            //遍历链表,根据key判断
            while(entry.next!=null){
                entry = entry.next;
                //判断key值是否相同
                if(entry.getKey().equals(k)){
                    return entry.getValue();
                }
            }
        }
        return null;
    }

JDK中的哈希表:

        在jdk早期哈希表主要是Hashtable

        之后为了提高效率,jdk提供了HashMap

                没有使用锁了

                可以对null值进行处理了

                提高了hash算法的效率

                在处理哈希冲突的时候,也使用链表法,但是在链表数量超过八个,会自动装换成红黑树

图:

        图是一种网状结构,和树结构最大的区别就是可以有多个父类节点

                图主要是由顶点(Vertex)和边(Edge)组成的

                边是顶点和顶点之间的连线

                边可以是有向的也可以是无向的

                图可以带权重,例如A到B所花的时间

                路径是一个顶点到另一个顶点所经过的边的序列

public class MyGraph {
    //存储顶点
    Vertex[] vertices;
    //需要一个矩阵用于存储边
    int[][] adjMat;
    int verNum;//顶点的数量
    public MyGraph(int num){
        //初始化数组存储顶点
        vertices = new Vertex[num];
        //初始化边
        adjMat = new int[num][num];
        verNum=0;
    }

    //添加顶点
    public void addVertex(String label){
        //创建订单对象
        Vertex vertex = new Vertex(label);
        vertices[verNum++] = vertex;
    }

    //添加边
    public void addEdge(String start,String end){
        int sIndex =0;//start这个顶点的坐标
        int eIndex =0;
        //遍历顶点
        for (int i=0;i<vertices.length;i++){
            if(vertices[i].label.equals(start)){
                //第一个顶点的坐标
                sIndex =i;
            }
            //找到第二个顶点的坐标
            if(vertices[i].label.equals(end)){
                eIndex =i;
            }
        }
        //定义边
        adjMat[sIndex][eIndex] = 1;
        adjMat[eIndex][sIndex] = 1;
    }

    //定义顶点类
    public static class Vertex{
        String label;//存储顶点的标签
        public Vertex(String label){
            this.label= label;
        }
    }
}

        两种遍历方法

                深度优先遍历:对每个可能的分支路径深入到不能在深为止,而且每个节点只能访问一次

public class MyGraph {
    //存储顶点
    Vertex[] vertices;
    //需要一个矩阵用于存储边
    int[][] adjMat;
    int verNum;//顶点的数量
    public MyGraph(int num){
        //初始化数组存储顶点
        vertices = new Vertex[num];
        //初始化边
        adjMat = new int[num][num];
        verNum=0;
    }

    //添加顶点
    public void addVertex(String label){
        //创建订单对象
        Vertex vertex = new Vertex(label);
        vertices[verNum++] = vertex;
    }

    //添加边
    public void addEdge(String start,String end){
        int sIndex =0;//start这个顶点的坐标
        int eIndex =0;
        //遍历顶点
        for (int i=0;i<vertices.length;i++){
            if(vertices[i].label.equals(start)){
                //第一个顶点的坐标
                sIndex =i;
            }
            //找到第二个顶点的坐标
            if(vertices[i].label.equals(end)){
                eIndex =i;
            }
        }
        //定义边
        adjMat[sIndex][eIndex] = 1;
        adjMat[eIndex][sIndex] = 1;
    }

    //定义顶点类
    public static class Vertex{
        String label;//存储顶点的标签
        public Vertex(String label){
            this.label= label;
        }
    }
}

广渡优先遍历:

                过程检验来说是对每一层的节点依次访问,访问完一层到下一层,每个节点都只访问一次(队列实现)

    //广度优先遍历
    public void bfs(int pos) {
        //创建队列
        Queue queue = new LinkedList<>();
        queue.add(pos);//入队列
        System.out.println(vertices[pos].label);//打印
        vertices[pos].isVisited = true;
        int current = pos;
        while(!queue.isEmpty()) {
            current = (int) queue.poll();//出列
            for (int i=0;i< vertices.length; i++) {
                if(adjMat[current][i]==1&&vertices[i].isVisited==false) {
                    //入队列
                    queue.add(i);//入队列
                    System.out.println(vertices[i].label);//打印
                    vertices[i].isVisited = true;
                }
            }
        }
    }

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值