数据结构_基础结构
鏈表
鏈表是一種數據結構,相對于數組而言,插入和刪除的開銷比較小,而查找的代價較大.以下我們實現雙向鏈表:
public class MyList<T> {private Node<T> head;private Node<T> tail;private int size;//初始化頭結點和尾節點public MyList(){head=new Node<T>(null, null, tail);tail=new Node<T>(null, head, null);}//獲得大小public int size(){return size;}//增加方法public void add(T x){this.addBefore(tail, x);}public void addBefore(int index,T x){this.addBefore(this.getNode(index), x);}public void addBefore(Node<T> p,T x){Node<T> newNode=new Node<T>(x, p.prev, p);newNode.prev.next=newNode;p.prev=newNode;size++;}//查找方法 效率應該高點public Node<T> getNode(int index){Node<T> p;if(index<size()/2){p=head.next;for(int i=0;i<index;i++){p=p.next;}}else{p=tail;for(int i=size();i>index;i--){p=p.prev;}}return p;}//刪除 public T remove(int index){return remove(this.getNode(index));}public T remove(Node<T> p){p.next.prev=p.prev;p.prev.next=p.next;size--;return p.data;}//修改public void set(int index,T newData){Node<T> p=this.getNode(index);p.data=newData;}//顯示全部public void show(){if(size()>0){Node<T> p=head.next;while(p!=tail){System.out.println(p.data);p=p.next;}}}//節點類private static class Node<T>{public T data;public Node<T> prev;public Node<T> next;public Node(T d,Node<T> p,Node<T> n){data=d;prev=p;next=n;}}}在我寫的這個雙向鏈表中頭節點head和尾節點tail是空出來的,不存放任何數據.若是要改造成雙向循環鏈表的話就不合適了,比如最后一個元素要next三下越過tail和head才循環到第一個元素.
于是稍作改造(注意空指針),雙向循環鏈表:
棧
棧Stack是一個表,先進后出,有兩種實現方式:由鏈表或者數組來實現.這里我們選用數組實現做例子,比較簡單:
package com.fredal.structure;public class MyStack<T> {// Java 不支持泛型數組,如需使用,請使用Java提供的容器private Object[] stack;// 棧的默認初始大小private static final int INIT_SIZE = 2;// 棧頂索引private int index;public MyStack() {stack = new Object[INIT_SIZE];index = -1;}/*** 構造方法* * @param initSize* 棧的初始大小*/public MyStack(int initSize) {if (initSize < 0) {throw new IllegalArgumentException();}stack = new Object[initSize];index = -1;}/*** 出棧操作* * @return 棧頂對象*/public T pop() {if (!isEmpty()) {T temp = peek();stack[index--] = null;return temp;}return null;}/*** 入棧操作* * @param obj* 等待入棧的對象*/public void push(T obj) {if (isFull()) {Object[] temp = stack;// 擴容操作,和arraylist的一樣,如果棧滿,則創建空間為當前棧空間兩倍的棧stack = new Object[2 * stack.length];System.arraycopy(temp, 0, stack, 0, temp.length);}stack[++index] = obj;}/*** 查看棧頂對象* * @return 棧頂對象*/public T peek() {if (!isEmpty()) {return (T) stack[index];}return null;}/*** 查看棧是否為空* * @return 如果棧為空返回true,否則返回false*/public boolean isEmpty() {return index == -1;}/*** 查看棧是否滿* * @return 如果棧滿返回true,否則返回false*/public boolean isFull() {return index >= stack.length - 1;} }應用的比較多的一個是檢測平衡符號,這個比較簡單,就是按順序push按順序pop看看是否相同.另外一個是逆波蘭表達式,寫一段代碼,包括中序表達式轉化為逆波蘭式,以及逆波蘭式的計算:
package com.fredal.structure;import java.util.ArrayList; import java.util.List; import java.util.Stack;public class Nbl {private static Stack s1 = new Stack();// 逆波蘭表達式的棧private static Stack s2 = new Stack();// 運算棧//將字符串轉化成中序listpublic static List<String> format(String s) {List<String> ls = new ArrayList<String>();// 存儲中序表達式int i = 0;String str;char c;do {if ((c = s.charAt(i)) < 48 || (c = s.charAt(i)) > 57) {ls.add("" + c);i++;} else {str = "";while (i < s.length() && (c = s.charAt(i)) >= 48&& (c = s.charAt(i)) <= 57) {str += c;i++;}ls.add(str);}} while (i < s.length());return ls;}//將中序表達式轉化為逆波蘭式public static List<String> parse(List<String> ls) {List<String> lss = new ArrayList<String>();for (String ss : ls) {if (ss.matches("\\d+")) {lss.add(ss);// 不是運算符就加入list} else if (ss.equals("(")) {s1.push(ss);//碰到"("直接進棧} else if(ss.equals(")")){while(!s1.peek().equals("(")){//將直到"("的符號全彈出lss.add((String) s1.pop());//加入list}s1.pop();//彈出"("}else{while(s1.size()!=0&&getValue((String) s1.peek())>=getValue(ss)){//比它優先級高的全彈出lss.add((String) s1.pop());}s1.push(ss);}}while(s1.size()!=0){//結束之后全部彈出lss.add((String) s1.pop()); }return lss;}//獲取運算符優先級private static int getValue(String s) {if (s.equals("+")) {return 1;} else if (s.equals("-")) {return 1;} else if (s.equals("*")) {return 2;} else if (s.equals("/")) {return 2;}return 0;}// 對逆波蘭表達式進行求值public static int calculate(List<String> ls) {for (String s : ls) {if (s.matches("\\d+")) {s2.push(s);// 不是運算符就入棧} else {int b = Integer.parseInt((String) s2.pop());int a = Integer.parseInt((String) s2.pop());if (s.equals("+")) {a = a + b;} else if (s.equals("-")) {a = a - b;} else if (s.equals("*")) {a = a * b;} else if (s.equals("/")) {a = a / b;}s2.push("" + a);}}return Integer.parseInt((String) s2.pop());} }隊列
隊列(queue)也是表,使用隊列時插入在一端進行而刪除在另一端進行.
基本操作是enqueue入隊,在表的末端插入元素.dequeue出隊,刪除并返回表的開頭.
用數組實現隊列會有潛在問題,就是隊列滿了之后在入隊一次back的下標會指向不存在的位置,解決這個問題我們用循環隊列的方式.
樹
先說二叉樹,二叉樹表示每個節點都不能有多于兩個的兒子.二叉樹的一個重要應用是二叉排序樹ADT:
package com.fredal.structure;public class MyTree<T extends Comparable<T>> {// 節點類private static class BNode<T> {T element;BNode<T> left;BNode<T> right;public BNode(T element) {this(element, null, null);}public BNode(T element, BNode<T> lt, BNode<T> rt) {this.element = element;left = lt;right = rt;}}// 插入節點之后public BNode<T> insert(T x, BNode<T> t) {if (t == null) {return new BNode<T>(x, null, null);}int result = x.compareTo(t.element);if (result < 0)t.left = insert(x, t.left);else if (result > 0)t.right = insert(x, t.right);return t;}// 刪除public BNode<T> remove(T x, BNode<T> t) {if (t == null)return t;int result = x.compareTo(t.element);if (result < 0)t.left = remove(x, t.left);else if (result > 0)t.right = remove(x, t.right);else if (t.left != null && t.right != null) {// 找到了 兩個孩子t.element = findMin(t.right).element;// 雖然是奇怪的刪除策略t.right = remove(t.element, t.right);} else// 找到了 一個孩子或沒有孩子t = (t.left != null) ? t.left : t.right;return t;}// 尋找最小值public BNode<T> findMin(BNode<T> t) {if (t == null)return null;else if (t.left == null)return t;return findMin(t.left);}// 尋找最大值public BNode<T> findMax(BNode<T> t) {if (t == null)return null;else if (t.right == null)return t;return findMax(t.right);}// 獲得樹的高度public int getHeight(BNode<T> t) {int a = 0;int b = 0;if (t.left != null)a = getHeight(t.left);if (t.right != null)b = getHeight(t.right);return (a > b ? a : b) + 1;}// 是否包含某個元素public boolean contains(T x, BNode<T> t) {if (t == null)return false;int result = x.compareTo(t.element);if (result < 0)return contains(x, t.left);else if (result > 0)return contains(x, t.right);elsereturn true;}// 顯示 中序遍歷了public void show(BNode<T> t) {if (t.left != null)show(t.left);System.out.println(t.element);if (t.right != null)show(t.right);} }還有一個就是所謂的表達式樹,一個正常的表達式構建的表達式樹如果采取中序遍歷就是得到正常的表達式,如果采取后序遍歷呢就會產生上一節說的那個逆波蘭表達式,也叫后綴表達式.
基本上這個樹的作用就是把后序表達式變成中序表達式,之前那段代碼逆作用..
用后綴表達式構建樹,再中序遍歷之
表達式樹確實是這樣的,中序遍歷也沒錯,不過要變成正常的表達式仍然是不夠的,還需要考慮優先級去加括號,偷懶方法當然是全部加上括號,這兒不寫了.
然后寫一下多叉樹的實現吧,這兒寫習慣了就給node外面加了一層包裝類,這樣添加的時候還是比較麻煩的,待優化吧.
散列
散列也叫哈希,會遇到散列沖突問題,解決沖突最常用的是分離鏈接法,簡單來說就是將散列到同一個值的所有元素都插入到一個鏈表中去,來實現吧.
package com.fredal.structure;import java.util.LinkedList; import java.util.List;public class MyHashTable<T> {private static final int DEFAULT_SIZE=100;private int size;private List<T>[] lists;public MyHashTable(int size){this.size=size;lists=new LinkedList[size];//初始化鏈表數組for (int i = 0; i < lists.length; i++) {lists[i]=new LinkedList<T>();}}public MyHashTable(){this(DEFAULT_SIZE);}private int myHash(T x){int hashVal=x.hashCode();hashVal%=lists.length;if(hashVal<0){hashVal+=lists.length;}return hashVal;}//是否包含 使用自帶的就好 ~public boolean contains(T x){List<T> list=lists[myHash(x)];return list.contains(x);}//插入方法public void insert(T x){List<T> list=lists[myHash(x)];if(!list.contains(x)){list.add(x);size++;}}//移除方法public void remove(T x){List<T> list=lists[myHash(x)];if(list.contains(x)){list.remove(x);size--;}} }這里并沒有考慮再散列,因為場景不復雜,再散列以后再談.
還有一種散列表叫探測散列表,也以后再說...
堆
堆也叫優先隊列,是允許至少下列兩種操作的數據結構,插入和刪除并返回最小值.插入(insert)相當于enqueue(入隊),而刪除最小值(deleteMin)則等價于隊列運算dequeue(出隊).
我們要講的叫二叉堆,除了是一顆完整二叉樹外還要保持其堆序性質.例如最小二叉堆即,最小元在根上,而任意節點都小于它的所有后裔.
我們通過代碼模擬二叉堆
堆的應用有許多,堆排序是最重要的之一,我們在后面會講到.
鏈表
鏈表是一種數據結構,相對于數組而言,插入和刪除的開銷比較小,而查找的代價較大.以下我們實現雙向鏈表:
public class MyList<T> {private Node<T> head;private Node<T> tail;private int size;//初始化頭結點和尾節點public MyList(){head=new Node<T>(null, null, tail);tail=new Node<T>(null, head, null);}//獲得大小public int size(){return size;}//增加方法public void add(T x){this.addBefore(tail, x);}public void addBefore(int index,T x){this.addBefore(this.getNode(index), x);}public void addBefore(Node<T> p,T x){Node<T> newNode=new Node<T>(x, p.prev, p);newNode.prev.next=newNode;p.prev=newNode;size++;}//查找方法 效率應該高點public Node<T> getNode(int index){Node<T> p;if(index<size()/2){p=head.next;for(int i=0;i<index;i++){p=p.next;}}else{p=tail;for(int i=size();i>index;i--){p=p.prev;}}return p;}//刪除 public T remove(int index){return remove(this.getNode(index));}public T remove(Node<T> p){p.next.prev=p.prev;p.prev.next=p.next;size--;return p.data;}//修改public void set(int index,T newData){Node<T> p=this.getNode(index);p.data=newData;}//顯示全部public void show(){if(size()>0){Node<T> p=head.next;while(p!=tail){System.out.println(p.data);p=p.next;}}}//節點類private static class Node<T>{public T data;public Node<T> prev;public Node<T> next;public Node(T d,Node<T> p,Node<T> n){data=d;prev=p;next=n;}}}在我寫的這個雙向鏈表中頭節點head和尾節點tail是空出來的,不存放任何數據.若是要改造成雙向循環鏈表的話就不合適了,比如最后一個元素要next三下越過tail和head才循環到第一個元素.
于是稍作改造(注意空指針),雙向循環鏈表:
棧
棧Stack是一個表,先進后出,有兩種實現方式:由鏈表或者數組來實現.這里我們選用數組實現做例子,比較簡單:
package com.fredal.structure;public class MyStack<T> {// Java 不支持泛型數組,如需使用,請使用Java提供的容器private Object[] stack;// 棧的默認初始大小private static final int INIT_SIZE = 2;// 棧頂索引private int index;public MyStack() {stack = new Object[INIT_SIZE];index = -1;}/*** 構造方法* * @param initSize* 棧的初始大小*/public MyStack(int initSize) {if (initSize < 0) {throw new IllegalArgumentException();}stack = new Object[initSize];index = -1;}/*** 出棧操作* * @return 棧頂對象*/public T pop() {if (!isEmpty()) {T temp = peek();stack[index--] = null;return temp;}return null;}/*** 入棧操作* * @param obj* 等待入棧的對象*/public void push(T obj) {if (isFull()) {Object[] temp = stack;// 擴容操作,和arraylist的一樣,如果棧滿,則創建空間為當前棧空間兩倍的棧stack = new Object[2 * stack.length];System.arraycopy(temp, 0, stack, 0, temp.length);}stack[++index] = obj;}/*** 查看棧頂對象* * @return 棧頂對象*/public T peek() {if (!isEmpty()) {return (T) stack[index];}return null;}/*** 查看棧是否為空* * @return 如果棧為空返回true,否則返回false*/public boolean isEmpty() {return index == -1;}/*** 查看棧是否滿* * @return 如果棧滿返回true,否則返回false*/public boolean isFull() {return index >= stack.length - 1;} }應用的比較多的一個是檢測平衡符號,這個比較簡單,就是按順序push按順序pop看看是否相同.另外一個是逆波蘭表達式,寫一段代碼,包括中序表達式轉化為逆波蘭式,以及逆波蘭式的計算:
package com.fredal.structure;import java.util.ArrayList; import java.util.List; import java.util.Stack;public class Nbl {private static Stack s1 = new Stack();// 逆波蘭表達式的棧private static Stack s2 = new Stack();// 運算棧//將字符串轉化成中序listpublic static List<String> format(String s) {List<String> ls = new ArrayList<String>();// 存儲中序表達式int i = 0;String str;char c;do {if ((c = s.charAt(i)) < 48 || (c = s.charAt(i)) > 57) {ls.add("" + c);i++;} else {str = "";while (i < s.length() && (c = s.charAt(i)) >= 48&& (c = s.charAt(i)) <= 57) {str += c;i++;}ls.add(str);}} while (i < s.length());return ls;}//將中序表達式轉化為逆波蘭式public static List<String> parse(List<String> ls) {List<String> lss = new ArrayList<String>();for (String ss : ls) {if (ss.matches("\\d+")) {lss.add(ss);// 不是運算符就加入list} else if (ss.equals("(")) {s1.push(ss);//碰到"("直接進棧} else if(ss.equals(")")){while(!s1.peek().equals("(")){//將直到"("的符號全彈出lss.add((String) s1.pop());//加入list}s1.pop();//彈出"("}else{while(s1.size()!=0&&getValue((String) s1.peek())>=getValue(ss)){//比它優先級高的全彈出lss.add((String) s1.pop());}s1.push(ss);}}while(s1.size()!=0){//結束之后全部彈出lss.add((String) s1.pop()); }return lss;}//獲取運算符優先級private static int getValue(String s) {if (s.equals("+")) {return 1;} else if (s.equals("-")) {return 1;} else if (s.equals("*")) {return 2;} else if (s.equals("/")) {return 2;}return 0;}// 對逆波蘭表達式進行求值public static int calculate(List<String> ls) {for (String s : ls) {if (s.matches("\\d+")) {s2.push(s);// 不是運算符就入棧} else {int b = Integer.parseInt((String) s2.pop());int a = Integer.parseInt((String) s2.pop());if (s.equals("+")) {a = a + b;} else if (s.equals("-")) {a = a - b;} else if (s.equals("*")) {a = a * b;} else if (s.equals("/")) {a = a / b;}s2.push("" + a);}}return Integer.parseInt((String) s2.pop());} }隊列
隊列(queue)也是表,使用隊列時插入在一端進行而刪除在另一端進行.
基本操作是enqueue入隊,在表的末端插入元素.dequeue出隊,刪除并返回表的開頭.
用數組實現隊列會有潛在問題,就是隊列滿了之后在入隊一次back的下標會指向不存在的位置,解決這個問題我們用循環隊列的方式.
樹
先說二叉樹,二叉樹表示每個節點都不能有多于兩個的兒子.二叉樹的一個重要應用是二叉排序樹ADT:
package com.fredal.structure;public class MyTree<T extends Comparable<T>> {// 節點類private static class BNode<T> {T element;BNode<T> left;BNode<T> right;public BNode(T element) {this(element, null, null);}public BNode(T element, BNode<T> lt, BNode<T> rt) {this.element = element;left = lt;right = rt;}}// 插入節點之后public BNode<T> insert(T x, BNode<T> t) {if (t == null) {return new BNode<T>(x, null, null);}int result = x.compareTo(t.element);if (result < 0)t.left = insert(x, t.left);else if (result > 0)t.right = insert(x, t.right);return t;}// 刪除public BNode<T> remove(T x, BNode<T> t) {if (t == null)return t;int result = x.compareTo(t.element);if (result < 0)t.left = remove(x, t.left);else if (result > 0)t.right = remove(x, t.right);else if (t.left != null && t.right != null) {// 找到了 兩個孩子t.element = findMin(t.right).element;// 雖然是奇怪的刪除策略t.right = remove(t.element, t.right);} else// 找到了 一個孩子或沒有孩子t = (t.left != null) ? t.left : t.right;return t;}// 尋找最小值public BNode<T> findMin(BNode<T> t) {if (t == null)return null;else if (t.left == null)return t;return findMin(t.left);}// 尋找最大值public BNode<T> findMax(BNode<T> t) {if (t == null)return null;else if (t.right == null)return t;return findMax(t.right);}// 獲得樹的高度public int getHeight(BNode<T> t) {int a = 0;int b = 0;if (t.left != null)a = getHeight(t.left);if (t.right != null)b = getHeight(t.right);return (a > b ? a : b) + 1;}// 是否包含某個元素public boolean contains(T x, BNode<T> t) {if (t == null)return false;int result = x.compareTo(t.element);if (result < 0)return contains(x, t.left);else if (result > 0)return contains(x, t.right);elsereturn true;}// 顯示 中序遍歷了public void show(BNode<T> t) {if (t.left != null)show(t.left);System.out.println(t.element);if (t.right != null)show(t.right);} }還有一個就是所謂的表達式樹,一個正常的表達式構建的表達式樹如果采取中序遍歷就是得到正常的表達式,如果采取后序遍歷呢就會產生上一節說的那個逆波蘭表達式,也叫后綴表達式.
基本上這個樹的作用就是把后序表達式變成中序表達式,之前那段代碼逆作用..
用后綴表達式構建樹,再中序遍歷之
表達式樹確實是這樣的,中序遍歷也沒錯,不過要變成正常的表達式仍然是不夠的,還需要考慮優先級去加括號,偷懶方法當然是全部加上括號,這兒不寫了.
然后寫一下多叉樹的實現吧,這兒寫習慣了就給node外面加了一層包裝類,這樣添加的時候還是比較麻煩的,待優化吧.
散列
散列也叫哈希,會遇到散列沖突問題,解決沖突最常用的是分離鏈接法,簡單來說就是將散列到同一個值的所有元素都插入到一個鏈表中去,來實現吧.
package com.fredal.structure;import java.util.LinkedList; import java.util.List;public class MyHashTable<T> {private static final int DEFAULT_SIZE=100;private int size;private List<T>[] lists;public MyHashTable(int size){this.size=size;lists=new LinkedList[size];//初始化鏈表數組for (int i = 0; i < lists.length; i++) {lists[i]=new LinkedList<T>();}}public MyHashTable(){this(DEFAULT_SIZE);}private int myHash(T x){int hashVal=x.hashCode();hashVal%=lists.length;if(hashVal<0){hashVal+=lists.length;}return hashVal;}//是否包含 使用自帶的就好 ~public boolean contains(T x){List<T> list=lists[myHash(x)];return list.contains(x);}//插入方法public void insert(T x){List<T> list=lists[myHash(x)];if(!list.contains(x)){list.add(x);size++;}}//移除方法public void remove(T x){List<T> list=lists[myHash(x)];if(list.contains(x)){list.remove(x);size--;}} }這里并沒有考慮再散列,因為場景不復雜,再散列以后再談.
還有一種散列表叫探測散列表,也以后再說...
堆
堆也叫優先隊列,是允許至少下列兩種操作的數據結構,插入和刪除并返回最小值.插入(insert)相當于enqueue(入隊),而刪除最小值(deleteMin)則等價于隊列運算dequeue(出隊).
我們要講的叫二叉堆,除了是一顆完整二叉樹外還要保持其堆序性質.例如最小二叉堆即,最小元在根上,而任意節點都小于它的所有后裔.
我們通過代碼模擬二叉堆
堆的應用有許多,堆排序是最重要的之一,我們在后面會講到.
更多文章與相關下載請點擊(http://langgengxin.com/2016/01/29/Structure-2/)
轉載于:https://www.cnblogs.com/fredal/p/Structure_2.html
總結
- 上一篇: Apache 配置虚拟主机
- 下一篇: 2015-01-14