數據結構與算法(二)

鏈表

  鏈表(Linked list)是一種常見的基礎數據結構,是一種線性表,可是並不會按線性的順序存儲數據,而是在每個節點裏存到下一個節點的指針(Pointer)。java

  使用鏈表結構能夠克服數組鏈表須要預先知道數據大小的缺點,鏈表結構能夠充分利用計算機內存空間,實現靈活的內存動態管理。可是鏈表失去了數組隨機讀取的優勢,同時鏈表因爲增長告終點的指針域,空間開銷比較大。node

  單向鏈表實現算法

public class SingleLinkedList<T>{

    private int size;

    private Node<T> head;

    public SingleLinkedList(){
        size =0;
        head = null;
    }

    private class Node<T>{
        private T data;
        private Node next;

        public Node(T t){
            data = t;
        }
    }

    public T addHead(T t){
        Node node = new Node(t);
        if(head==null){
            head =node;
        }else{
            node.next = head;
            head = node;
        }
        size++;
        return t;
    }


    public boolean deleteHead(){
        if(isEmpty()){
            return false;
        }
        T value = head.data;
        head = head.next;
        size--;
        return true;

    }

    public boolean deleteNode(T t){
        boolean value = false;
        if(isEmpty()){
            return value;
        }
        Node  current = head;
        Node previous = head;
        while(!t.equals(current.data)){
            if(current.next==null){
               value =false;
               break;
            }else{
                previous = current;
                current = current.next;
            }
        }
        if(current == head){
            head = current.next;
            size--;
        }else{
            previous.next = current.next;
            size--;
        }
        return value;

    }



    public boolean contain(T t){
        boolean value = false;
        if(isEmpty()){
            return value;
        }
        Node  node = head;
        while(node != null ){
            if(t.equals(node.data)){
                value =true;
                break;
            }
            node = node.next;
        }
        return value;
    }

    public boolean isEmpty(){
        return size==0;
    }

    public static void main(String[] args) {
        SingleLinkedList<String> stringSingleLinkedList = new SingleLinkedList<>();
        stringSingleLinkedList.addHead("asd");
        stringSingleLinkedList.addHead("zcx");
        stringSingleLinkedList.addHead("qwe");
        System.out.println(stringSingleLinkedList.contain("zcx"));
        stringSingleLinkedList.deleteNode("zcx");
        System.out.println(stringSingleLinkedList.contain("zcx"));
        System.out.println(stringSingleLinkedList.isEmpty());
        stringSingleLinkedList.deleteHead();
        System.out.println(stringSingleLinkedList.contain("qwe"));
        System.out.println(stringSingleLinkedList.isEmpty());
        System.out.println(stringSingleLinkedList.contain("asd"));
    }
}

  利用單項鍊表實現棧shell

public class SingleLinkStack<T> {

    private SingleLinkedList<T> stringSingleLinkedList = new SingleLinkedList<>();


    public void push(T t){
        stringSingleLinkedList.addHead(t);
    }

    public boolean poll(){
        return stringSingleLinkedList.deleteHead();
    }

    public boolean isEmpty(){
        return stringSingleLinkedList.isEmpty();
    }
}

  雙端鏈表,注意和雙向鏈表的區別數組

public class DoublePointLinkedList<T>{

    private int size;

    private Node<T> head;

    private Node<T> tail;

    public DoublePointLinkedList(){
        size =0;
        tail =head = null;
    }

    private class Node<T>{
        private T data;
        private Node next;

        public Node(T t){
            data = t;
        }
    }

    public T addHead(T t){
        Node node = new Node(t);
        if(head==null){
            tail = head =node;
        }else{
            node.next = head;
            head = node;
        }
        size++;
        return t;
    }

    public T addTail(T t){
        Node node = new Node(t);
        if(tail == null){
            tail = head =node;
        }else{
            tail.next = node;
            tail = node;
        }
        size++;
        return t;
    }


    public T deleteHead(){
        if(isEmpty()){
            return null;
        }
        T t = head.data;
        if(head.next==null){
            tail =head =null;
        }else{
            head = head.next;
        }
        size--;
        return t;

    }

    public boolean deleteNode(T t){
        boolean value = false;
        if(isEmpty()){
            return value;
        }
        Node  current = head;
        Node previous = head;
        while(!t.equals(current.data)){
            if(current.next==null){
               value =false;
               break;
            }else{
                previous = current;
                current = current.next;
            }
        }
        if(current == head){
            head = current.next;
            size--;
        }else{
            previous.next = current.next;
            size--;
        }
        return value;

    }



    public boolean contain(T t){
        boolean value = false;
        if(isEmpty()){
            return value;
        }
        Node  node = head;
        while(node != null ){
            if(t.equals(node.data)){
                value =true;
                break;
            }
            node = node.next;
        }
        return value;
    }

    public void display(){
        if(isEmpty()){
            return;
        }else {
            Node node = head;
            while(node !=null){
                System.out.println(node.data);
                node = node.next;
            }
        }
    }


    public boolean isEmpty(){
        return size==0;
    }

    public static void main(String[] args) {
        DoublePointLinkedList<String> doublePointLinkedList = new DoublePointLinkedList<>();
        doublePointLinkedList.addHead("asd");
        doublePointLinkedList.addHead("zcx");
        doublePointLinkedList.addHead("qwe");
        doublePointLinkedList.addTail("vbn");
        doublePointLinkedList.display();
    }
}

  利用雙端鏈表實現隊列數據結構

public class LinkedListQueue <T>{

    private DoublePointLinkedList<T> doublePointLinkedList = new DoublePointLinkedList<T>();

    public void add(T t){
        doublePointLinkedList.addTail(t);
    }

    public T poll(){
        return doublePointLinkedList.deleteHead();
    }

    public static void main(String[] args) {
        LinkedListQueue<String> linkedListQueue = new LinkedListQueue<>();
        linkedListQueue.add("asd");
        linkedListQueue.add("qwe");
        linkedListQueue.add("zxc");
        System.out.println(linkedListQueue.poll());
        System.out.println(linkedListQueue.poll());
        System.out.println(linkedListQueue.poll());

    }
}

  利用鏈表實現優先級隊列測試

public class OrderSingleLinkedList<T extends Comparable>{

    private int size;

    private Node<T> head;

    public OrderSingleLinkedList(){
        size =0;
        head = null;
    }

    private class Node<T extends Comparable>{
        private T data;
        private Node next;

        public Node(T t){
            data = t;
        }
    }

    public T addHead(T t){
        Node node = new Node(t);
        if(head==null){
            head =node;
        }else{
            node.next = head;
            head = node;
        }
        size++;
        return t;
    }

    public void insert(T t){
        Node node = new Node(t);
        if(isEmpty()){
            head = node;
            size++;
            return;
        }
        Node previous = null;
        Node current = head;
        while(current!=null&& (current.data.compareTo(t)<0)){
            previous = current;
            current =current.next;
        }
        if(previous==null){
            head = node;
            node.next = current;
        }else{
            previous.next = node;
            node.next = current;
        }
        size++;
    }


    public boolean deleteHead(){
        if(isEmpty()){
            return false;
        }
        head = head.next;
        size--;
        return true;

    }

    public boolean deleteNode(T t){
        boolean value = false;
        if(isEmpty()){
            return value;
        }
        Node  current = head;
        Node previous = head;
        while(!t.equals(current.data)){
            if(current.next==null){
               value =false;
               break;
            }else{
                previous = current;
                current = current.next;
            }
        }
        if(current == head){
            head = current.next;
            size--;
        }else{
            previous.next = current.next;
            size--;
        }
        return value;

    }



    public boolean contain(T t){
        boolean value = false;
        if(isEmpty()){
            return value;
        }
        Node  node = head;
        while(node != null ){
            if(t.equals(node.data)){
                value =true;
                break;
            }
            node = node.next;
        }
        return value;
    }

    public void display(){
        if(isEmpty()){
            return;
        }else {
            Node node = head;
            while(node !=null){
                System.out.println(node.data);
                node = node.next;
            }
        }
    }

    public boolean isEmpty(){
        return size==0;
    }

    public static void main(String[] args) {

        OrderSingleLinkedList<String> orderSingleLinkedList = new OrderSingleLinkedList<>();
        orderSingleLinkedList.insert("asd");
        orderSingleLinkedList.insert("zcx");
        orderSingleLinkedList.insert("qwe");
        orderSingleLinkedList.insert("awe");
        orderSingleLinkedList.insert("zxs");
        orderSingleLinkedList.display();

    }
}

  雙向鏈表ui

public class TwoWayLinkedList<T> {

    private Node head;
    private Node tail;
    private int size =0;

    public TwoWayLinkedList(){

        tail = head =null;
    }

    private class Node<T>{
        private T data;
        private Node next;
        private Node previous;

        public Node(T t){
            data = t;
        }
    }

    public void addHead(T t){
        Node node = new Node(t);
        if(head == null){
            tail = head = node;
        }else{
            head.previous = node;
            node.next = head;
            head = node;
        }
        size++;
    }

    public void addTail(T t){
        Node node = new Node(t);
        if(tail ==null){
            head = tail = node;
        }else{
            tail.next = node;
            node.previous = tail;
            tail = node;
        }
        size++;
    }

    public T deleteHead(){
        Node<T> temp = head;
        if(head!=null){
            head = head.next;
            T t = temp.data;
            temp.data= null;
            size--;
            return t;
        }else{
            return null;
        }
    }

    public void forwardDisplay(){
        Node node = head;
        while (node!= null){
            System.out.println(node.data);
            node = node.next;
        }
    }
    public void backWarddisplay(){
        Node node = tail;
        while (node!= null){
            System.out.println(node.data);
            node = node.previous;
        }
    }

    public static void main(String[] args) {
        TwoWayLinkedList<String> twoWayLinkedList = new TwoWayLinkedList<>();
        twoWayLinkedList.addHead("qwe");
        twoWayLinkedList.addHead("asd");
        twoWayLinkedList.addTail("zxc");
        twoWayLinkedList.forwardDisplay();
        System.out.println("------------------");
        twoWayLinkedList.backWarddisplay();
    }
}

遞歸

  一個遞歸方法每次都是用不一樣的參數值反覆調用本身,當某種參數值使得遞歸的方法返回,而再也不調用自身,這種狀況稱爲邊界值,也叫基值。當遞歸方法返回時,遞歸過程經過逐漸完成各層方法實例的未執行部分,而從最內層返回到最外層的原始調用處。編碼

  階乘、漢諾塔、歸併排序等均可以用遞歸來實現,可是要注意任何能夠用遞歸完成的算法用棧都能實現。當咱們發現遞歸的方法效率比較低時,能夠考慮用循環或者棧來代替它。spa

  利用遞歸首先歸併排序

public class MergeSort {

    public static int[] mergeSort(int[] c,int start,int last){
        if(last > start){
            //也能夠是(start+last)/2,這樣寫是爲了防止數組長度很大形成二者相加超過int範圍,致使溢出
            int mid = start + (last - start)/2;
            mergeSort(c,start,mid);//左邊數組排序
            mergeSort(c,mid+1,last);//右邊數組排序
            merge(c,start,mid,last);//合併左右數組
        }
        return c;
    }

    public static void merge(int[] c,int start,int mid,int last){
        int[] temp = new int[last-start+1];//定義臨時數組
        int i = start;//定義左邊數組的下標
        int j = mid + 1;//定義右邊數組的下標
        int k = 0;
        while(i <= mid && j <= last){
            if(c[i] < c[j]){
                temp[k++] = c[i++];
            }else{
                temp[k++] = c[j++];
            }
        }
        //把左邊剩餘數組元素移入新數組中
        while(i <= mid){
            temp[k++] = c[i++];
        }
        //把右邊剩餘數組元素移入到新數組中
        while(j <= last){
            temp[k++] = c[j++];
        }

        //把新數組中的數覆蓋到c數組中
        for(int k2 = 0 ; k2 < temp.length ; k2++){
            c[k2+start] = temp[k2];
        }
    }

    public static void main(String[] args) {
        int[] array ={3,2,5,6,8,7,9,1,4};
        Arrays.stream(array).forEach(System.out::println);
        array = mergeSort(array,0,array.length-1);
        Arrays.stream(array).forEach(System.out::println);
    }
}

  二分查找也能夠利用遞歸實現,而且便於理解可是效率比較低,下面使用循環實現二分查找

public class TwoPointSelect {


    public static int twoPoint (int[] array,int key){
        int start = 0;
        int end = array.length-1;
        while(start<=end){
            int middle =(end-start)/2 + start;
            if(key == array[middle]){
                return middle;
            }
            if(key <array[middle]){
                end = middle-1;
            }
            if(key >array[middle]){
                start = middle +1;
            }
        }
        return -1;
    }

    public static void main(String[] args) {
        int[] array ={1,2,3,4,5,6,7,8,9};
        System.out.println(twoPoint(array,5));
    }
}

  樹的經常使用術語

  一、路徑:順着節點的邊從一個節點走到另外一個節點,所通過的節點的順序排列就稱爲「路徑」。

  二、根:樹頂端的節點稱爲根。一棵樹只有一個根,若是要把一個節點和邊的集合稱爲樹,那麼從根到其餘任何一個節點都必須有且只有一條路徑。A是根節點。

  三、父節點:若一個節點含有子節點,則這個節點稱爲其子節點的父節點;B是D的父節點。

  四、子節點:一個節點含有的子樹的根節點稱爲該節點的子節點;D是B的子節點。

  五、兄弟節點:具備相同父節點的節點互稱爲兄弟節點;好比上圖的D和E就互稱爲兄弟節點。

  六、葉節點:沒有子節點的節點稱爲葉節點,也叫葉子節點,好比上圖的H、E、F、G都是葉子節點。

  七、子樹:每一個節點均可以做爲子樹的根,它和它全部的子節點、子節點的子節點等都包含在子樹中。

  八、節點的層次:從根開始定義,根爲第一層,根的子節點爲第二層,以此類推。

  九、深度:對於任意節點n,n的深度爲從根到n的惟一路徑長,根的深度爲0;

  十、高度:對於任意節點n,n的高度爲從n到一片樹葉的最長路徑長,全部樹葉的高度爲0;

  二叉樹:樹的每一個節點最多隻能有兩個子節點。

  二叉搜索樹要求:若它的左子樹不空,則左子樹上全部結點的值均小於它的根結點的值; 若它的右子樹不空,則右子樹上全部結點的值均大於它的根結點的值; 它的左、右子樹也分別爲二叉排序樹。

  遍歷樹是根據一種特定的順序訪問樹的每個節點。比較經常使用的有前序遍歷,中序遍歷和後序遍歷。而二叉搜索樹最經常使用的是中序遍歷。

  一、中序遍歷:左子樹——》根節點——》右子樹

  二、前序遍歷:根節點——》左子樹——》右子樹

  三、後序遍歷:左子樹——》右子樹——》根節點

  樹是由邊和節點構成,根節點是樹最頂端的節點,它沒有父節點;二叉樹中,最多有兩個子節點;某個節點的左子樹每一個節點都比該節點的關鍵字值小,右子樹的每一個節點都比該節點的關鍵字值大,那麼這種樹稱爲二叉搜索樹,其查找、插入、刪除的時間複雜度都爲logN;能夠經過前序遍歷、中序遍歷、後序遍從來遍歷樹,前序是根節點-左子樹-右子樹,中序是左子樹-根節點-右子樹,後序是左子樹-右子樹-根節點;刪除一個節點只須要斷開指向它的引用便可;哈夫曼樹是二叉樹,用於數據壓縮算法,最常常出現的字符編碼位數最少,不多出現的字符編碼位數多一些。  

  二叉搜索樹對於某個節點而言,其左子樹的節點關鍵值都小於該節點關鍵值,右子樹的全部節點關鍵值都大於該節點關鍵值。二叉搜索樹做爲一種數據結構,其查找、插入和刪除操做的時間複雜度都爲O(logn),底數爲2。可是咱們說這個時間複雜度是在平衡的二叉搜索樹上體現的,也就是若是插入的數據是隨機的,則效率很高,可是若是插入的數據是有序的,好比從小到大的順序【10,20,30,40,50】插入到二叉搜索樹中,則數據全在右邊,而從大到小插入就是所有在左邊,這和鏈表沒有任何區別了,這種狀況下查找的時間複雜度爲O(N),而不是O(logN)。固然這是在最不平衡的條件下,實際狀況下,二叉搜索樹的效率應該在O(N)和O(logN)之間,這取決於樹的不平衡程度。

  那麼爲了可以以較快的時間O(logN)來搜索一棵樹,咱們須要保證樹老是平衡的(或者大部分是平衡的),也就是說每一個節點的左子樹節點個數和右子樹節點個數儘可能相等。紅-黑樹的就是這樣的一棵平衡樹,對一個要插入的數據項(刪除也是),插入例程要檢查會不會破壞樹的特徵,若是破壞了,程序就會進行糾正,根據須要改變樹的結構,從而保持樹的平衡。

  紅黑樹的特徵:

  一、節點都有顏色;

  二、在插入和刪除的過程當中,要遵循保持這些顏色的不一樣排列規則,具體以下

  ①每一個節點不是紅色就是黑色的;

  ②根節點老是黑色的;

  ③若是節點是紅色的,則它的子節點必須是黑色的(反之不必定),(也就是從每一個葉子到根的全部路徑上不能有兩個連續的紅色節點);

  ④從根節點到葉節點或空子節點的每條路徑,必須包含相同數目的黑色節點(即相同的黑色高度);從根節點到葉節點的路徑上的黑色節點的數目稱爲黑色高度,規則 4 另外一種表示就是從根到葉節點路徑上的黑色高度必須相同。

  注意:新插入的節點顏色老是紅色的,這是由於插入一個紅色節點比插入一個黑色節點違背紅-黑規則的可能性更小,緣由是插入黑色節點總會改變黑色高度(違背規則4),可是插入紅色節點只有一半的機會會違背規則3(由於父節點是黑色的沒事,父節點是紅色的就違背規則3)。另外違背規則3比違背規則4要更容易修正。

  具體紅黑樹的實現能夠參考java中TreeMap源碼。

  數據結構中樹除了以上結構外還有2-3-4樹,B樹,B+,B-樹等,還用用於表示更復雜的關係的圖等。

高級排序

  直接插入排序更適合於原始記錄基本有序的集合。這是由於若是記錄基本有序,那麼直接插入排序時移動的次數就會不多。而希爾排序正式利用了直接排序的這一個特色,希爾排序將數據按必定的步長進行分組,使得記錄很快就會達到總體基本有序。

  希爾排序的步長選擇是一個數學難題,根據經驗一般使用如下公式進行計算:while(step<array.length/3){step = step*3 +1;},每輪排序的步長是動態變化的。demo以下

public class ShellSort {

    public static void shellSort(int[] array){

        System.out.println(Arrays.toString(array));
        int step = 1;
        while(step<array.length/3){
            step = step*3 +1;
        }

        while(step>0){
            for(int i =step;i<array.length;i++){
                int temp = array[i];
                int j =i;
                while(j > step-1 && temp <= array[j-step]){
                    array[j] = array[j-step];
                    j -= step;
                }
                array[j] = temp;

            }
            System.out.println(Arrays.toString(array));
            step = (step-1)/3;
        }
        System.out.println(Arrays.toString(array));

    }

    public static void main(String[] args) {
        int[] array = {4,2,8,9,5,7,6,1,3,10};
        shellSort(array);
    }
}

  快速排序(Quicksort)是對冒泡排序的一種改進。它的基本思想是:經過一趟排序將要排序的數據分割成獨立的兩部分,其中一部分的全部數據都比另一部分的全部數據都要小,而後再按此方法對這兩部分數據分別進行快速排序,整個排序過程能夠遞歸進行,以此達到整個數據變成有序序列。demo以下

public class QuickSort {

    //數組array中下標爲i和j位置的元素進行交換
    private static void swap(int[] array , int i , int j){
        int temp = array[i];
        array[i] = array[j];
        array[j] = temp;
    }

    private static void recQuickSort(int[] array,int left,int right){
        if(right <= left){
            return;//終止遞歸
        }else{

            int partition = partitionIt(array,left,right);
            recQuickSort(array,left,partition-1);// 對上一輪排序(切分)時,基準元素左邊的子數組進行遞歸
            recQuickSort(array,partition+1,right);// 對上一輪排序(切分)時,基準元素右邊的子數組進行遞歸
        }
    }

    private static int partitionIt(int[] array,int left,int right){
        //爲何 j加一個1,而i沒有加1,是由於下面的循環判斷是從--j和++i開始的.
        //而基準元素選的array[left],即第一個元素,因此左遊標從第二個元素開始比較
        int i = left;
        int j = right+1;
        int pivot = array[left];// pivot 爲選取的基準元素(頭元素)
        int size = right - left + 1;
        if(size >= 3){
            pivot = medianOf3(array, left, right); //數組範圍大於3,基準元素選擇中間值。
        }
        while(true){
            while(i<right && array[++i] < pivot){}

            while(j > 0 && array[--j] > pivot){}

            if(i >= j){// 左右遊標相遇時候中止, 因此跳出外部while循環
                break;
            }else{
                swap(array, i, j);// 左右遊標未相遇時中止, 交換各自所指元素,循環繼續
            }
        }
        swap(array, left, j);//基準元素和遊標相遇時所指元素交換,爲最後一次交換
        return j;// 一趟排序完成, 返回基準元素位置(注意這裏基準元素已經交換位置了)
    }

    public static void sort(int[] array){
        recQuickSort(array, 0, array.length-1);
    }

    private static int medianOf3(int[] array,int left,int right){
        int center = (right-left)/2+left;
        if(array[left] > array[right]){ //獲得 array[left] < array[right]
            swap(array, left, right);
        }
        if(array[center] > array[right]){ //獲得 array[left] array[center] < array[right]
            swap(array, center, right);
        }
        if(array[center] > array[left]){ //獲得 array[center] <  array[left] < array[right]
            swap(array, center, left);
        }

        return array[left]; //array[left]的值已經被換成三數中的中位數, 將其返回
    }

    //測試
    public static void main(String[] args) {
        int[] array = {7,3,5,2,9,8,6,1,4,7};
        sort(array);
        for(int i : array){
            System.out.print(i+" ");
        }
    }

}
相關文章
相關標籤/搜索