链表
链表(Linked list)是一种常见的基础数据结构,是一种线性表,但是并不会按线性的顺序存储数据,而是在每一个节点里存到下一个节点的指针(Pointer)。
使用链表结构可以克服数组链表需要预先知道数据大小的缺点,链表结构可以充分利用计算机内存空间,实现灵活的内存动态管理。但是链表失去了数组随机读取的优点,同时链表由于增加了结点的指针域,空间开销比较大。
单向链表实现
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")); } }
利用单项链表实现栈
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(); } }
双向链表
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(); } }
递归
一个递归方法每次都是用不同的参数值反复调用自己,当某种参数值使得递归的方法返回,而不再调用自身,这种情况称为边界值,也叫基值。当递归方法返回时,递归过程通过逐渐完成各层方法实例的未执行部分,而从最内层返回到最外层的原始调用处。
阶乘、汉诺塔、归并排序等都可以用递归来实现,但是要注意任何可以用递归完成的算法用栈都能实现。当我们发现递归的方法效率比较低时,可以考虑用循环或者栈来代替它。
利用递归首先归并排序
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)); } }
树
树的常用术语
1、路径:顺着节点的边从一个节点走到另一个节点,所经过的节点的顺序排列就称为“路径”。
2、根:树顶端的节点称为根。一棵树只有一个根,如果要把一个节点和边的集合称为树,那么从根到其他任何一个节点都必须有且只有一条路径。A是根节点。
3、父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点;B是D的父节点。
4、子节点:一个节点含有的子树的根节点称为该节点的子节点;D是B的子节点。
5、兄弟节点:具有相同父节点的节点互称为兄弟节点;比如上图的D和E就互称为兄弟节点。
6、叶节点:没有子节点的节点称为叶节点,也叫叶子节点,比如上图的H、E、F、G都是叶子节点。
7、子树:每个节点都可以作为子树的根,它和它所有的子节点、子节点的子节点等都包含在子树中。
8、节点的层次:从根开始定义,根为第一层,根的子节点为第二层,以此类推。
9、深度:对于任意节点n,n的深度为从根到n的唯一路径长,根的深度为0;
10、高度:对于任意节点n,n的高度为从n到一片树叶的最长路径长,所有树叶的高度为0;
二叉树:树的每个节点最多只能有两个子节点。
二叉搜索树要求:若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值; 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值; 它的左、右子树也分别为二叉排序树。
遍历树是根据一种特定的顺序访问树的每一个节点。比较常用的有前序遍历,中序遍历和后序遍历。而二叉搜索树最常用的是中序遍历。
1、中序遍历:左子树——》根节点——》右子树
2、前序遍历:根节点——》左子树——》右子树
3、后序遍历:左子树——》右子树——》根节点
树是由边和节点构成,根节点是树最顶端的节点,它没有父节点;二叉树中,最多有两个子节点;某个节点的左子树每个节点都比该节点的关键字值小,右子树的每个节点都比该节点的关键字值大,那么这种树称为二叉搜索树,其查找、插入、删除的时间复杂度都为logN;可以通过前序遍历、中序遍历、后序遍历来遍历树,前序是根节点-左子树-右子树,中序是左子树-根节点-右子树,后序是左子树-右子树-根节点;删除一个节点只需要断开指向它的引用即可;哈夫曼树是二叉树,用于数据压缩算法,最经常出现的字符编码位数最少,很少出现的字符编码位数多一些。
二叉搜索树对于某个节点而言,其左子树的节点关键值都小于该节点关键值,右子树的所有节点关键值都大于该节点关键值。二叉搜索树作为一种数据结构,其查找、插入和删除操作的时间复杂度都为O(logn),底数为2。但是我们说这个时间复杂度是在平衡的二叉搜索树上体现的,也就是如果插入的数据是随机的,则效率很高,但是如果插入的数据是有序的,比如从小到大的顺序【10,20,30,40,50】插入到二叉搜索树中,则数据全在右边,而从大到小插入就是全部在左边,这和链表没有任何区别了,这种情况下查找的时间复杂度为O(N),而不是O(logN)。当然这是在最不平衡的条件下,实际情况下,二叉搜索树的效率应该在O(N)和O(logN)之间,这取决于树的不平衡程度。
那么为了能够以较快的时间O(logN)来搜索一棵树,我们需要保证树总是平衡的(或者大部分是平衡的),也就是说每个节点的左子树节点个数和右子树节点个数尽量相等。红-黑树的就是这样的一棵平衡树,对一个要插入的数据项(删除也是),插入例程要检查会不会破坏树的特征,如果破坏了,程序就会进行纠正,根据需要改变树的结构,从而保持树的平衡。
红黑树的特征:
1、节点都有颜色;
2、在插入和删除的过程中,要遵循保持这些颜色的不同排列规则,具体如下
①每个节点不是红色就是黑色的;
②根节点总是黑色的;
③如果节点是红色的,则它的子节点必须是黑色的(反之不一定),(也就是从每个叶子到根的所有路径上不能有两个连续的红色节点);
④从根节点到叶节点或空子节点的每条路径,必须包含相同数目的黑色节点(即相同的黑色高度);从根节点到叶节点的路径上的黑色节点的数目称为黑色高度,规则 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+" "); } } }