算法第六章
树结构
非线性
树结构本身是一种天然的组织结构 (文件目录结构,企业层级结构)
将数据使用树结构存储后,出奇的高效
二分搜索树
二叉树:和链表一样,是动态的数据结构
二叉树具有唯一跟节点(左孩子和右孩子)
二叉树具有天然递归结构(链表是线性的,循环更好处理)
二叉树不一定是满的
二分搜索树是二叉树
二分搜索树的每个节点的值:
大于其左子树的所有节点的值
小于其右子树的所有节点的值
存储的元素必须具有可比较性
我们的二分搜索树不包含重复元素
如果想包含重复元素的话,只需要定义:
左子树小于等于节点,或者右子树大于等于节点
二分搜素树的遍历
所有节点都访问一遍。
后序遍历的一个应用(为二分搜索树释放内存)
深度优先遍历(前序,中序,后序)
层序遍历(通常使用非递归)
二分搜索树具有顺序性 ,同时可以拿到前驱和后继。拿到一个数的floor和tail
二分搜索树代码实现:
public class BST<E extends Comparable<E>> {
private class Node{
public E e;
public Node left,right;
public Node(E e){
this.e=e;
left=null;
right=null;
}
}
private Node root;
private int size;
public BST(){
root=null;
size=0;
}
public int getSize(){
return size;
}
public boolean isEmpty(){
return size==0;
}
public void add(E e){
root=add(root,e);
}
//向以node为跟的二分搜索树种插入元素E,递归算法
private Node add(Node node,E e){
/*if(e.equals(node.e)){
return;
}else if(e.compareTo(node.e)<0&& node.left==null){
node.left=new Node(e);
size++;
return;
}else if(e.compareTo(node.e)>0&&node.right==null){
node.right=new Node(e);
size++;
return;
}*/
if(node==null){
size++;
return new Node(e);
}
if(e.compareTo(node.e)<0){
node.left=add(node.left,e);
}else if(e.compareTo(node.e)>0){
node.right=add(node.right,e);
}
return node;
}
//看二分搜索树种是否包含元素e
public boolean contains(E e){
return contains(root,e);
}
private boolean contains(Node node,E e){
if(node==null){
return false;
}
if(e.compareTo(node.e)==0){
return true;
}else if(e.compareTo(node.e)<0){
return contains(node.left,e);
}else{
return contains(node.right,e);
}
}
//二分搜索树的前序遍历
public void preOrder(){
preOrder(this.root);
}
private void preOrder(Node node){
if(node==null){
return;
}
System.out.println(node.e);
preOrder(node.left);
preOrder(node.right);
}
public void inOrder(){
inOrder(this.root);
}
private void preOrderNR(){
Stack<Node> stack=new Stack<>();
stack.push(root);
while (!stack.isEmpty()){
Node cur=stack.pop();
System.out.println(cur.e);
if(cur.right!=null){
stack.push(cur.right);
}
if(cur.left!=null){
stack.push(cur.left);
}
}
}
private void inOrder(Node node){
if(node==null){
return;
}
inOrder(node.left);
System.out.println(node.e);
inOrder(node.right);
}
public static void main(String[] args) {
// 5
// 3 6
// 2 4 8
BST<Integer> bst=new BST<>();
int[] nums={5,3,6,8,4,2};
for (int num:
nums) {
bst.add(num);
}
bst.remove(3);
//bst.preOrder();
//System.out.println(bst.toString());
//bst.inOrder();
//bst.preOrderNR();
bst.levelOrder();
/* Random random=new Random();
int n=1000;
for (int i=0;i<n;i++){
bst.add(random.nextInt(10000));
}
ArrayList<Integer> nums=new ArrayList<>();
while (!bst.isEmpty()){
nums.add(bst.removeMin());
}
System.out.println(nums);
for (int i=1;i<nums.size();i++){
if(nums.get(i-1)>nums.get(i)){
throw new IllegalArgumentException("ERROR");
}
}
System.out.println("test completed");*/
}
//二分搜索树的层序遍历
public void levelOrder(){
Queue<Node> queue=new LinkedList<>();
queue.add(root);
while (!queue.isEmpty()){
Node cur= queue.remove();
System.out.println(cur.e);
if(cur.left!=null){
queue.add(cur.left);
}
if(cur.right!=null){
queue.add(cur.right);
}
}
}
//寻找二分搜索树的最小元素
public E minimum(){
if(size==0){
throw new IllegalArgumentException("BST is empty");
}
return minimum(root).e;
}
//返回以node为根的二分搜索树的最小值所在的节点
private Node minimum(Node node) {
if(node.left==null){
return node;
}
return minimum(node.left);
}
//寻找二分搜索树的最小元素
public E maximum(){
if(size==0){
throw new IllegalArgumentException("BST is empty");
}
return minimum(root).e;
}
//返回以node为根的二分搜索树的最小值所在的节点
private Node maximum(Node node) {
if(node.right==null){
return node;
}
return maximum(node.right);
}
public E removeMin(){
E ret=minimum();
root=removeMin(root);
return ret;
}
//删除掉以node为根的二分搜索树中最小节点
//返回删除节点后新的二分搜索树的根
private Node removeMin(Node node){
//删掉当前节点
if(node.left==null){
//如果node.right也等于null
Node rightNode=node.right;
node.right=null;
size--;
return rightNode;
}
node.left=removeMin(node.left);
return node;
}
public E removeMax(){
E ret=minimum();
root=removeMax(root);
return ret;
}
//删除掉以node为根的二分搜索树中最大节点
//返回删除节点后新的二分搜索树的根
private Node removeMax(Node node){
//删掉当前节点
if(node.right==null){
Node rightNode=node.left;
node.left=null;
size--;
return rightNode;
}
node.right=removeMax(node.right);
return node;
}
//从二分搜索树中删除元素为e的节点
public void remove(E e){
root=remove(root,e);
}
//删除掉以node为根的二分搜索树中值为e的节点,递归算法
//返回删除节点后新的二分搜索树的根
private Node remove(Node node, E e) {
if(node==null){
return null;
}
//e<node.e 要删除的数小于当前数
if(e.compareTo(node.e)<0){
node.left=remove(node.left,e);
return node;
}else if(e.compareTo(node.e)>0){
node.right=remove(node.right,e);
return node;
}else{
//查找到需要删除的数
//待删除节点左子树为空的情况,应该直接把删除节点右子树转接到该节点的位置
if(node.left==null){
Node rightNode=node.right;
node.right=null;
size--;
return rightNode;
}
//待删除节点右子树为空的情况
if(node.right==null){
Node leftNode=node.left;
node.left=null;
size--;
return leftNode;
}
//待删除节点左右子树均不为空的情况
//找到比待删除节点大的最小节点,即待删除节点右子树的最小节点
//用这个节点顶替待删除节点的位置
Node successor=minimum(node.right);
successor.right=removeMin(node.right);
successor.left=node.left;
node.left=node.right=null;
return successor;
}
}
@Override
public String toString(){
StringBuilder res=new StringBuilder();
generateBSTString(root,0,res);
return res.toString();
}
/**
* 自下而上创建,--递增(depth)
* @param node
* @param depth
* @param res
*/
private void generateBSTString(Node node, int depth, StringBuilder res) {
if(node==null){
res.append(generateDepthString(depth)+"null\n");
return;
}
//访问节点在访问其子节点之前,称为前序遍历 前中后 中序遍历 前后中 后序遍历
generateBSTString(node.left,depth+1,res);
res.append(generateDepthString(depth)+node.e+"\n");
generateBSTString(node.right,depth+1,res);
}
private String generateDepthString(int depth) {
StringBuilder res=new StringBuilder();
for (int i=depth;i>0;i--){
res.append("--");
}
return res.toString();
}
}
平衡二叉树
AVL 红黑树
算法第七章
集合
集合的时间复杂度
增,查,删O(h) h是二分搜索树的高度
平均O(logn)
有序集合和无序集合
有序集合中的元素具有顺序性
无需集合中的元素没有顺序性 —— 基于哈希表的表现
映射 Map
例子:字典(单词) 名册(身份证号) 车辆管理 数据库(id)
有序映射和无序映射
有序映射(键具有顺序性) —— 基于搜索树的实现
public class BSTMap<K extends Comparable,V> implements Map<K,V> {
private class Node{
public K key;
public V value;
public Node left,right;
public Node(K key,V value){
this.key=key;
this.value=value;
left=null;
right=null;
}
}
private Node root;
private int size;
public BSTMap() {
this.root = null;
this.size = 0;
}
@Override
public void add(K key, V value) {
root=add(root,key,value);
}
//向二分搜索树中添加新的元素(key,value))
private Node add(Node node, K key, V value) {
if(node==null){
size++;
return new Node(key,value);
}
if(key.compareTo(node.key)<0){
node.left=add(node.left,key,value);
}else if(key.compareTo(node.key)>0){
node.right=add(node.right,key,value);
}else{
node.value=value;
}
return node;
}
private Node getNode(Node node,K key){
if(node == null){
return null;
}
if(key.compareTo(node.key) == 0){
return node;
}else if(key.compareTo(node.key) < 0){
return getNode(node.left,key);
}else{
return getNode(node.right,key);
}
}
@Override
public V remove(K key) {
Node node=getNode(root,key);
if(node!=null){
root=remove(root,key);
return node.value;
}
return null;
}
private Node remove(Node node, K key) {
if(node==null){
return null;
}
//e<node.e 要删除的数小于当前数
if(key.compareTo(node.key)<0){
node.left=remove(node.left,key);
return node;
}else if(key.compareTo(node.key)>0){
node.right=remove(node.right,key);
return node;
}else{
//查找到需要删除的数
//待删除节点左子树为空的情况,应该直接把删除节点右子树转接到该节点的位置
if(node.left==null){
Node rightNode=node.right;
node.right=null;
size--;
return rightNode;
}
//待删除节点右子树为空的情况
if(node.right==null){
Node leftNode=node.left;
node.left=null;
size--;
return leftNode;
}
//待删除节点左右子树均不为空的情况
//找到比待删除节点大的最小节点,即待删除节点右子树的最小节点
//用这个节点顶替待删除节点的位置
Node successor=minimum(node.right);
successor.right=removeMin(node.right);
successor.left=node.left;
node.left=node.right=null;
return successor;
}
}
//删除掉以node为根的二分搜索树中最小节点
//返回删除节点后新的二分搜索树的根
private Node removeMin(Node node){
//删掉当前节点
if(node.left==null){
//如果node.right也等于null
Node rightNode=node.right;
node.right=null;
size--;
return rightNode;
}
node.left=removeMin(node.left);
return node;
}
//返回以node为根的二分搜索树的最小值所在的节点
private Node minimum(Node node) {
if(node.left==null){
return node;
}
return minimum(node.left);
}
@Override
public boolean contains(K key) {
return getNode(root,key) != null;
}
@Override
public V get(K key) {
Node node=getNode(root,key);
return node == null ? null : node.value;
}
@Override
public void set(K key, V newValue) {
Node node = getNode(root,key);
if(node == null){
throw new IllegalArgumentException(key+"doesn't exist!");
}
node.value=newValue;
}
@Override
public int getSize() {
return this.size;
}
@Override
public boolean isEmpty() {
return this.size==0;
}
}
无序映射 --------- 基于链表的实现
public class LinkedListMap<K,V> implements Map<K,V> {
private class Node{
public K key;
public V value;
public Node next;
public Node(K key,V value,Node next){
this.key=key;
this.value=value;
this.next=next;
}
public Node(K key){
this(key,null,null);
}
public Node(){
this(null,null,null);
}
@Override
public String toString(){
return this.key+":"+this.value;
}
}
private Node dummyHead;
private int size;
public LinkedListMap() {
this.dummyHead = new Node();
this.size = 0;
}
private Node getNode(K key){
Node cur=dummyHead.next;
while (cur!=null){
if(cur.key.equals(key)){
return cur;
}
cur=cur.next;
}
return null;
}
@Override
public void add(K key, V value) {
Node node=getNode(key);
if(node==null){
//添加在头部
dummyHead.next=new Node(key,value,dummyHead.next);
size++;
}else{
node.value=value;
}
}
@Override
public V remove(K key) {
//查找元素,查找到则退出
Node prev=dummyHead;
while (prev.next!=null){
if(prev.next.key.equals(key)){
break;
}
prev=prev.next;
}
if(prev.next!=null){
Node delNode=prev.next;
prev.next=delNode.next;
delNode.next=null;
return delNode.value;
}
return null;
}
@Override
public boolean contains(K key) {
return getNode(key)!=null;
}
@Override
public V get(K key) {
Node node=getNode(key);
return node==null?null:node.value;
}
@Override
public void set(K key, V newValue) {
Node node=getNode(key);
if(node==null){
throw new IllegalArgumentException(key+"doesn't exist!");
}
node.value=newValue;
}
@Override
public int getSize() {
return 0;
}
@Override
public boolean isEmpty() {
return false;
}
}
无序映射(没有顺序性) —— 基于哈希表的实现
第八章
优先队列
定义:出队顺序和入队顺序无关,和优先级相关
动态排序队列 (ai 优先队列取对应的敌人)
普通线性
顺序线性
堆(二叉堆)
一个堆本身也是一棵树
二叉堆是一颗完全二叉树 (如果最后一层节点不够,从左到右排序)
堆中某个节点的值总是不大于其父节点的值
完全二叉树 : 把元素顺序排列成树的形状
通过上浮完成堆的性质(当前节点小于其父节点)
下沉取出数据
Replace: 取出最大元素后,再放入一个新的元素
Heapify: 将任意数组整理成堆的形状(只要最后一个非叶子节点不断siftDown)
堆的代码表示:
public class MaxHeap<E extends Comparable<E>> {
private Array<E> data;
public MaxHeap(int capacity){
data=new Array<>(capacity);
}
public MaxHeap(){
data=new Array<>();
}
//返回堆中的元素个数
public int size(){
return data.getSize();
}
//返回一个布尔值,表示堆中是否为空
public boolean isEmpty(){
return data.isEmpty();
}
public int parent(int index){
if(index == 0){
throw new IllegalArgumentException("0 doesn't have parent");
}
return (index - 1)/2;
}
//返回完全二叉树的数组表示中,一个索引所表示的元素左孩子节点的索引
private int leftChild(int index){
return index * 2 + 1;
}
//返回完全二叉树的数组表示中,一个索引所表示的元素右孩子节点的索引
private int rightChild(int index){
return index * 2 + 2;
}
//向堆中添加元素
public void add(E e){
data.addLast(e);
siftUp(data.getSize()-1);
}
private void siftUp(int k) {
while (k>0 && data.get(parent(k)).compareTo(data.get(k)) < 0){
data.swap(k,parent(k));
}
}
public E findMax(){
if(data.getSize() == 0){
throw new IllegalArgumentException("Can not findMax when heap is empty");
}
return data.get(0);
}
public E extractMax(){
E ret=findMax();
data.swap(0,data.getSize() - 1);
data.removeLast();
siftDown(0);
return ret;
}
private void siftDown(int k) {
while (leftChild(k) < data.getSize()){
int j=leftChild(k);
if(j + 1 < data.getSize() && data.get(j + 1).compareTo(data.get(j)) > 0){
j=rightChild(k);
//data[j] 是 leftChild 和 rightChild 中的最大值
}
if(data.get(k).compareTo(data.get(j)) >= 0){
break;
}
data.swap(k,j);
k=j;
}
}
public static void main(String[] args) {
int n = 1000000;
MaxHeap<Integer> maxHeap = new MaxHeap<>();
Random random=new Random();
for (int i = 0;i < n;i ++){
maxHeap.add(random.nextInt(Integer.MAX_VALUE));
}
int[] arr = new int[n];
for (int i =0 ; i < n;i++){
arr[i] = maxHeap.extractMax();
}
System.out.println(arr[0]);
}
public E replace(E e){
E ret=findMax();
data.set(0,e);
siftDown(0);
return ret;
}
public MaxHeap(E[] arr){
data=new Array<>(arr);
for (int i=parent(arr.length - 1);i >= 0;i--){
siftDown(i);
}
}
算法第九章
线段树
m次染色后 我们可以看到多少种颜色
染色操作(更新区间)
查询操作(查询区间)
线段树不是完全二叉树
线段树是平衡二叉树(最大深度和最小深度差只能为1)
堆也是平衡二叉树
对区间进行操作,可以使用线段树
代码
public class SegmentTree<E> {
private E[] tree;
private E[] data;
private Merger<E> merger;
public SegmentTree(E[] arr,Merger<E> merger){
//创建一个与构造参数数组大小相同的成员变量数组
//将构造参数数组中的值传入成员变量数组
//对tree数组赋值 大小为4倍的arr.length
this.merger=merger;//用来表示两个区间如何融合
data=(E[])new Object[arr.length];
for (int i=0;i<arr.length;i++){
data[i]=arr[i];
}
tree= (E[]) new Object[4*arr.length];
buildSegmentTree(0,0,data.length-1);
}
private void buildSegmentTree(int treeIndex,int l,int r) {
//应考虑使用递归创建线段树,需要当前索引,以及区间范围
//递归考虑结束条件为递归到底
//如果没有递归到底,即当前索引一定有左右孩子,创建线段树的左右子树以及左右子树的区级范围
//上一级为下一级节点之和
if(l == r){
tree[treeIndex] = data[l];
return;
}
int leftTreeIndex=leftChild(treeIndex);
int rightTreeIndex=rightChild(treeIndex);
int mid = (l + r) / 2;
buildSegmentTree(leftTreeIndex,l,mid);
buildSegmentTree(rightTreeIndex,mid+1,r);
tree[treeIndex] = merger.merge(tree[leftTreeIndex] , tree[rightTreeIndex]);
}
public void set(int index,E e){
if(index < 0 || index >= data.length){
throw new IllegalArgumentException("Index is illegal");
}
data[index]=e;
set(0,0,data.length - 1,index,e);
}
private void set(int treeIndex, int l, int r, int index, E e) {
//遍历所有的节点,找到符合的节点并更新
if(l == r){
tree[treeIndex] = e;
return;
}
//计算出所需要的左中右节点
int mid = l+(r + l) / 2;
int leftTreeIndex=leftChild(treeIndex);
int rightTreeIndex=rightChild(treeIndex);
//根据index的索引,去对应区间,直到找到该值。
if(index >= mid+1){
set(rightTreeIndex,mid + 1,r,index,e);
}else //index <= mid
{
set(leftTreeIndex,l,mid,index,e);
}
//对线段树的区间的值进行更新。
tree[treeIndex] = merger.merge(tree[leftTreeIndex],tree[rightTreeIndex]);
}
public E query(int queryL,int queryR){
//对传入的值进行有效区级查询,分别有有效值校验以及r必须大于l
if(queryL<0||queryL>=data.length||
queryL<0||queryR>=data.length||
queryL>queryR){
throw new IllegalArgumentException("Index is illegal.");
}
return query(0,0,data.length-1,queryL,queryR);
}
//在以treeID为根的线段树中 l-r 的范围里,搜索区级区级(qeryL...queryR)的值
private E query(int treeIndex, int l, int r,int queryL,int queryR) {
//结束条件为l和r的信息与用户想要查找的queryL和queryR重合
if(l == queryL && r == queryR){
return tree[treeIndex];
}
//获取以treeID为根的线段是的左右子树节点,以及中间数
int mid = l + (r-l) / 2;
int leftTreeIndex = leftChild(treeIndex);
int rightTreeIndex=rightChild(treeIndex);
//如果搜索区间在左子树,右子树,或者都有应该怎么办
if(queryL >= mid + 1){
return query(rightTreeIndex,mid + 1,r,queryL,queryR);
}else if(queryR<=mid){
return query(leftTreeIndex,l,mid,queryL,queryR);
}
E leftResult = query(leftTreeIndex, l, mid, queryL, queryR);
E rightResult=query(rightTreeIndex,mid + 1,r,mid+1,queryR);
return merger.merge(leftResult,rightResult);
}
public E get(int index){
//判断当前索引是否合法
//返回索引所在位置的成员变量数组的值
if(index < 0 || index >= data.length){
throw new IllegalArgumentException("Index is illegal");
}
return data[index];
}
private int leftChild(int index){
return 2*index+1;
}
private int rightChild(int index){
return 2*index+2;
}
@Override
public String toString(){
StringBuilder res=new StringBuilder();
res.append("[");
for (int i=0;i<tree.length;i++){
if(tree[i]!=null){
res.append(tree[i]);
}else {
res.append("null");
}
/* if(i!=tree.length-1){
}*/
}
return res.toString();
}
/* public static void main(String[] args) {
Integer[] nums={-2,0,3,-5,2,-1};
SegmentTree<Integer> segmentTree=new SegmentTree<>(nums, new Merger<Integer>() {
@Override
public Integer merge(Integer a, Integer b) {
//求和返回a+b 或者max(a,b)
return a + b;
}
});
System.out.println(segmentTree.query(0,2));
}*/
}
第十章
Trie (多叉树)
专门真正给字典设计的数据结构(专门处理字符串)
时间复杂度为O(w) w为查询单词的长度
将字符串拆开以字母为单位
对于. 需要遍历所有的可能
trie的局限性:空间 改善:压缩字典树,把单链多个字符合并。
三分搜索树 <d ==d >d
public class Trie {
private class Node{
public boolean isWord;
public TreeMap<Character,Node> next;
public Node(boolean isWord){
this.isWord=isWord;
next=new TreeMap<>();
}
public Node(){
this(false);
}
}
private Node root;
private int size;
public Trie(){
root=new Node();
size=0;
}
//获得Trie中存储的单词数量
public int getSize(){
return size;
}
//向Trie中添加一个新的单词word
public void add(String word){
Node cur=root;
for (int i=0;i<word.length();i++){
char c=word.charAt(i);
//将字符添加到节点,如果节点中没有这个字符
if(cur.next.get(c) == null){
cur.next.put(c,new Node());
}
//将当前节点跳到下一个节点
cur = cur.next.get(c);
}
if(!cur.isWord){
cur.isWord = true;
size++;
}
}
//查询单词word是否在trie中
public boolean contains(String word){
//根据字符顺序,如果全部便利到,并且isWord为true,则返回包含该单词
Node cur=root;
for (int i = 0;i < word.length();i++){
char c=word.charAt(i);
if(cur.next.get(c) == null){
return false;
}
//来到了这个字符所在的节点
cur=cur.next.get(c);
}
//如果该节点isWord为true,则true
return cur.isWord;
}
public boolean match(String word){
return match(root,word,0);
}
//查询单词word是否在trie中
private boolean match(Node node,String word,int index){
//如果index与字符长度一致,且当前是一个单词,返回true;
//对所传入的字符进行匹配,如果不为.,匹配成功后进入下一个节点,如果为.,递归遍历接下来的所有可能,匹配到返回true
if(index == word.length()){
return node.isWord;
}
char c= word.charAt(index);
if(c != '.'){
if(node.next.get(c) == null){
return false;
}
return match(node.next.get(c),word,index+1);
}else {
for (char nextChar: node.next.keySet()){
if(match(node.next.get(nextChar),word,index+1)){
return true;
}
}
return false;
}
}
//查找是否在trie中有单词以prefix为前缀
public boolean isPrefix(String prefix){
//查找有没有这个前缀节点就行
Node cur=root;
for (int i=0;i<prefix.length();i++){
char c=prefix.charAt(i);
if(cur.next.get(c) == null){
return false;
}
cur = cur.next.get(c);
}
return true;
}
}
第十一章
并查集
一种孩子指向父亲的数据结构
并查集可以很好看出来网络中节点间的连接状态
网络是一个抽象的概念:用户之间形成的网络
连接问题和路径问题
每一个元素,看做是一个节点
基于size的优化,有可能节点多的深度比较小
提出 基于rank的优化。
路径压缩 :
路径压缩2 :直接指向根节点 (复杂度近乎是O(1))