算法--线性结构

一、数据结构

什么是数据结构:数据与数据之间的关系。

数据的存储结构:顺序存储(ArrayList)、链式存储(LinkList)。

数据的逻辑结构:集合结构、线性结构、树形结构、图形结构。

二、算法

算法:解决问题的方法。

算法的特性:输入、输出、有穷、确定性、可行性。

算法的基本要求:正确性、可读性、健壮性、时间复杂度、空间复杂度。

三、线性结构

1、数组

普通数组:类似Java基本类型数组

对象数组:类似ArrayList在对象里面定义一个数组类型的成员变量。

数组中的算法:线性查找和二分法查找。

//线性查找
public static void queryByIndex(int[] arr){
    int target = 4;
int index = -1;
for(int i=0;i<arr.length;i++){
if(target == arr[i]){
index = i;
break;
}
}
System.out.println(index);
}

  

/**
 * 二分法查找,前提是数组的内容是有序的
 * @param arr 数组
 */
public static void queryByMid(int[] arr){
    int target = 4;
    int index = -1;
    int begin = 0;
    int end = arr.length - 1;
    int mid;
    while(true){
        mid = (begin+end)/2;
        if(target == arr[mid]){
            index = mid;
            break;
        }else if(target < arr[mid]){
            end = mid - 1;
        }else{
            begin = mid + 1;
        }
        //不加这行会死循环
        if(begin >= end){
            break;
        }
    }

    System.out.println(index);
}
2、栈

特点:先入后出,可以使用数组对象模拟。

class Stack{
    int[] elements = new int[0];
    /**
     * 数组越界空指针异常不考虑
     * @param element
     */
    public void push(int element){
        int[] arr = new int[elements.length + 1];
        System.arraycopy(elements,0,arr,0,elements.length);
        arr[arr.length -1] = element;
        elements = arr;
    }
    /**
     * 数组越界空指针异常不考虑
     * @return
     */
    public int pop(){
        int[] arr = new int[elements.length - 1];
        int len = elements.length;
        int value = elements[len -1];
        System.arraycopy(elements,0,arr,0,arr.length);
        elements = arr;
        return value;
    }
    public static void main(String[] args){
        Stack stack = new Stack();
        stack.push(1);
        stack.push(2);
        stack.push(3);
        stack.push(4);
        System.out.println(stack.pop());
        System.out.println(stack.pop());
    }
}
3、队列

特点:先进先出,可以使用数组对象模拟。

class Queue{
    int[] elements = new int[0];
    /**
     * 数组越界空指针异常不考虑
     * @param element
     */
    public void push(int element){
        int[] arr = new int[elements.length + 1];
        System.arraycopy(elements,0,arr,0,elements.length);
        arr[arr.length -1] = element;
        elements = arr;
    }
    /**
     * 数组越界空指针异常不考虑
     * @return
     */
    public int pop(){
        int[] arr = new int[elements.length - 1];
        int value = elements[0];
        System.arraycopy(elements,1,arr,0,arr.length);
        elements = arr;
        return value;
    }
    public static void main(String[] args){
        Queue queue = new Queue();
        queue.push(2);
        queue.push(3);
        queue.push(4);
        System.out.println(queue.pop());
        System.out.println(queue.pop());
        System.out.println(queue.elements.length);
    }
}
4、单向链表

特点:一个节点除存储数据以外,还需存储下一个节点的指向。

class Node{
    //节点数据
    int data;
    /**
     * java对象在内存中是地址,可以理解下个节点的地址
     */
    Node next;

    public Node(int data){
        this.data = data;
    }

    public Node append(Node node){
        Node currentNode = this;
        while(true){
            Node nextNode = currentNode.next;
            if(null == nextNode){
                break;
            }
            currentNode = nextNode;
        }
        //当前节点是可变的,因为循环中重新赋值了
        currentNode.next = node;
        return this;
    }

    public Node next(){
        return this.next;
    }

    public int getData(){
        return this.data;
    }
    public boolean isLast(){
        if(null == this.next){
            return true;
        }
        return false;
    }
    /**
     * 移除也只能移除当前节点的下个节点
     */
    public void removeNext(){
        Node deleteNode = this.next;
        Node newNext = deleteNode.next();
        this.next = newNext;
    }

    /**
     * 只能插入当前节点后面
     * @param node
     */
    public void inset(Node node){
        Node next = this.next;
        this.next = node;
        node.next = next;
    }

    public static void main(String[] args) {
        Node node =new Node(1);
        node.append(new Node(2)).append(new Node(3));
        System.out.println(node.next().getData());

    }
}
5、单项循环链表

特点:和单链表相比,尾节点的下个节点指向首节点,构成一个单向循环。

class Node{
    //节点数据
    int data;
    //java对象在内存中是地址,可以理解下个节点的地址
    Node next = this;

    public Node(int data){
        this.data = data;
    }

    public Node next(){
        return this.next;
    }

    public int getData(){
        return this.data;
    }

    /**
     * 移除也只能移除当前节点的下个节点
     */
    public void removeNext(){
        Node deleteNode = this.next;
        Node newNext = deleteNode.next();
        this.next = newNext;
    }

    /**
     * 只能插入当前节点后面
     * @param node
     */
    public void inset(Node node){
        Node next = this.next;
        this.next = node;
        node.next = next;
    }

    public static void main(String[] args) {
        Node node =new Node(1);
        Node node2 =new Node(2);
        Node node3 =new Node(3);
        System.out.println(node.next().getData());
        node.inset(node2);
        node.inset(node3);
        System.out.println(node.next().getData());
        System.out.println(node3.next().getData());
    }
}
6、双向循环链表

特点:由数据、前一个节点和下个节点做成

class DoubleNode{
    
    DoubleNode pre = this;

    DoubleNode next = this;

    int data;

    public DoubleNode(int data){
        this.data = data;
    }

    /**
     * 往后增加节点
     * @param node
     */
    public void after(DoubleNode node){
        //当前节点的下个节点
        DoubleNode oldNext = this.next;
        //新增的节点
        this.next = node;
        
        node.pre = this;
        node.next = oldNext;
        
        oldNext.pre = node;
    }
}

  

7、递归

特点:在一个方法或者函数内部调用该方法,记得留出口,不然会栈溢出(StackOverflowError)。

/**
 * 斐波那契数列 1 1 2 3 5 8 ...
 */
public static int test(int i) {
    if (1 == i || 2 == i) {
        return 1;
    }
    return test(i - 1) + test(i - 2);
}

  

汉诺塔
/**
 * 汉诺塔
 * 无论有多少盘子,都认为只有两个,上面的所有盘子和下面一个盘子。
 *
 * @param n    共n个盘子
 * @param from 开始位置
 * @param in   中间位置
 * @param to   目标位置
 */
public static void test2(int n, char from, char in, char to) {
    if (1 == n) {
        System.out.println("第1个盘子从" + from + "移动到 " + to);
    } else {
        //移动上面的盘子到中间位置
        test2(n - 1, from, to, in);
        System.out.println("第" + n + "盘子从" + from + "移动到 " + to);
        //把上面的所有盘子从中间位置移动到目标位置
        test2(n - 1, in, from, to);
    }
}

  

四、算法

特点:时间复杂度、空间复杂度。

常见的时间复杂度

常数阶O(1)、对数阶O(log2n)、线性阶O(n)、线性对数阶O(nlog2n)

平方阶O(n^2)、立方阶O(n^3)、k次方阶O(n^k)、指数阶O(2n)

随着问题规模n的不断增大,上述时间复杂度不断增大,算法的执行效率越低。

计算时间复杂度:用常数1代替运行时间中所有加法常数,修改后的运行次数函数中,只保留高阶项,去除最高阶项的系数。列如T(n) = n^2+5n+6与T(n)=3n^2+3n+2这两个函数,他们的复杂度都是O(n^2)。

平均时间复杂度:是指所有可能的输入实列均以等概率出现的情况下,该算法的运行时间。

最坏时间复杂度:是算法在任何输入实列上运行时间的界限,这就保证了算法的运行时间不会比最坏情况更长。

八种常用排序算法

交换排序:冒泡排序和快速排序

冒泡排序
 /**
     * 就像水中的泡泡每次都是最大的先出水面
     * @param arr
     */
    public static void bubbleSort(int[] arr){
        int swap = arr[0];
        //控制比较多少轮
        for(int i= 0;i < arr.length -1;i++){
            //控制比较次数
            for(int j=0;j < arr.length-i-1;j++){
                if(arr[j] > arr[j + 1]){
                    swap = arr[j];
                    arr[j] = arr[j + 1];
                    arr[j+1] = swap;
                }
            }
        }
    }
快速排序 思想类似递归
public static void quickSort(int[] arr,int low,int high){
        int i,j,temp,t;
        if(low>high){
            return;
        }
        i=low;
        j=high;
        //temp就是基准位
        temp = arr[low];

        while (i<j) {
            //先看右边,依次往左递减
            while (temp<=arr[j]&&i<j) {
                j--;
            }
            //再看左边,依次往右递增
            while (temp>=arr[i]&&i<j) {
                i++;
            }
            //如果满足条件则交换
            if (i<j) {
                t = arr[j];
                arr[j] = arr[i];
                arr[i] = t;
            }

        }
        //最后将基准为与i和j相等位置的数字交换
        arr[low] = arr[i];
        arr[i] = temp;
        //递归调用左半数组
        quickSort(arr, low, j-1);
        //递归调用右半数组
        quickSort(arr, j+1, high);
    }


    public static void main(String[] args) {
        int[] arr = new int[]{3,2,6,4,2,1};
        Test.quickSort(arr,0,arr.length-1);
        Arrays.stream(arr).forEach(item -> System.out.println(item));
    }
 

插入排序:直接插入排序和希尔排序

//插入排序,下标前面的数据都是有序的
public static void insertSort(int[] arr){
    //编列所有数字
    for(int i=1;i<arr.length;i++){
        //如果当前数字比前一个数字小
        if(arr[i]<arr[i-1]){
            int temp = arr[i];
            int j;
            //遍历当前数字前面的所有数字
            for(j = i-1;j>=0 && temp<arr[j];j--){
                //把前一个数字赋值给后一个数字
                arr[j+1] = arr[j];
            }
            把临时变量(外层for循环的当前元素)赋给不满足条件的后一个元素
            arr[j+1] = temp;
        }
    }
}
//希尔排序 最要概念是步长
public static void shellSort(int[] arr){
    int k=1;
    //编列所有的步长
    for(int d = arr.length/2;d > 0;d/=2){
        //遍历所有元素
       for(int i=d;i<arr.length;i++){
           //编列本组中所有元素
           for(int j = i-d;j>=0;j-=d){
               if(arr[j]>arr[j+d]){
                   int temp = arr[j];
                   arr[j] = arr[j+d];
                   arr[j+d] = temp;
               }
           }
       }
        System.out.println(k+Arrays.toString(arr));
        k++;
    }
}

  

选择排序:简单选择排序

//选择排序 每次循环记录最小下标
public static void selectSort(int[] arr){
  
    //控制比较多少轮
    for(int i= 0;i < arr.length -1;i++){
        int minIndex=i;
        //把当前遍历的数和后面所有的数依次比较,并记录最小的下标
        for(int j = i+1;j < arr.length-i;j++){
            if(arr[minIndex] > arr[j]){
                //记录下最小的下标数
               minIndex = j;
            }
        }
        //交换位置
        if(i!=minIndex){
            int temp = arr[i];
            arr[i]=arr[minIndex];
            arr[minIndex]=arr[i];
        }
    }
}


//归并排序
public static void merge(int[] arr,int low,int middle,int hight){
    //用于存储归并后的临时数组
    int[] temp = new int[hight-low+1];
    //记录第一个数组中需要遍历的下标
    int i = low;
    //记录第二个数组中需要遍历的下标
    int j = middle+1;
    //用于记录在临时数组中存放的下标
    int index = 0;
    //遍历两个数组取出小的数字,放入临时数组
    while(i<=middle&&j<=hight){
        //第一个数组的数据更小
        if(arr[i]<=arr[j]){
            //把小的数据放入临时数组中
            temp[index] = arr[i];
            //让下标向后移动一位
            i++;
        }else{
            temp[index] = arr[j];
            j++;
        }
        index++;
    }
    //处理多余的数据
    while(j<=hight){
        temp[index] = arr[i];
        j++;
        index++;
    }
    while(i<-middle){
        temp[index]=arr[i];
        i++;
        index++;
    }
    //把临时数组中的数组重新存入原数组
    for(int k=0;k<temp.length;k++){
        arr[k+low] = temp[k];
    }
}

  

基数排序

//基数排序,可支持队列
public static void radixSort(int[] arr){
    
    int max = Integer.MIN_VALUE;
    for(int i=0;i<arr.length;i++){
        if(arr[i]>max){
            max = arr[i];
        }
    }
     
    int maxLength = (max+"").length();
    //用于临时存储数据的二维数组
    int[][]  temp = new int[10][arr.length];
    int[] count = new int[10];
    for(int i=0;i<maxLength;i++,n*=10){
        for(in j=0;j<arr.length;j++){
            //计算余数
            int ys = arr[j]/n%10;
            temp[ys][count[ys]] = arr[j];
            //记录数量
            count[ys]++;
        }
        //记录取的元素需要放的位置
        int index=0;
        //把数字取出
        for(int k=0;k<count.length;k++){
            if(count[k]!=0){
                for(int l=0;l<count[k];l++){
                    arr[index++] = temp[k][l];
                }
            }
            count[k] = 0;
        }
    }
    
}

  

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

posted @ 2020-03-18 10:51  码农的进击  阅读(430)  评论(0编辑  收藏  举报