1.冒泡排序
2.选择排序
3.插入排序
4.希尔排序
5.归并排序
6.快速排序
7.线性表
8.顺序表
9.链表
10.栈
11.队列
12.符号表
13.二叉树
14.堆
15.优先队列
16.2-3 树
17.红黑树
18.B-树
19.B+树
20.并查集
21.图
22.有向图
一. 冒泡排序
排序原理:
1.比较相邻的元素,如果前一个元素比后一个元素大,就交换这两个元素的位置
2.对每一个相邻元素做同样的工作,从开始第一对元素到结尾的最后一个元素,最终最后位置的元素就是最大值
冒泡排序API设计
public class Bubble {
/**
* 对数组a中的元素进行排序
*/
public static void sort(Comparable[] a) {
for (int i = a.length - 1; i > 0; i--) {
for (int j = 0; j < i; j++) {
// 比较索引j和索引j+1处的值
if (greater(a[j], a[j + 1])) {
exch(a, j, j + 1);
}
}
}
}
/**
* 比较v元素是否大于w元素
*/
private static boolean greater(Comparable v, Comparable w) {
return v.compareTo(w) > 0;
}
/**
* 数组元素i和j交换位置
*/
private static void exch(Comparable[] a, int i, int j) {
Comparable temp;
temp = a[i];
a[i] = a[j];
a[j] = temp;
}
}
二. 选择排序
排序原理:
1.每一次遍历的过程中,都假定第一个索引处的元素是最小值,和其他索引处的值依次进行比较,如果当前索引处的值大于其他某个索引处的值,则假定其他某个索引处的值为最小值,最后可以找到最小值所在的索引
2.交换第一个索引处和最小值所在的索引处的值
选择排序API设计
public class Selection {
/**
* 对数组a中的元素进行排序
*/
public static void sort(Comparable[] a) {
for (int i = 0; i <= a.length - 2; i++) {
//定义一个变量,记录最小元素所在的索引,默认为参与选择排序的第一个元素所在的位置
int minIndex = i;
for (int j = i + 1; j < a.length; j++) {
//需要比较最小索引minIndex处的值和j索引处的值
if (greater(a[minIndex], a[j])) {
minIndex = j;
}
}
//交换最小元素所在索引minIndex处的值和索引i的值
exch(a, i, minIndex);
}
}
/**
* 比较v元素是否大于w元素
*/
private static boolean greater(Comparable v, Comparable w) {
return v.compareTo(w) > 0;
}
/**
* 数组元素i和j交换位置
*/
private static void exch(Comparable[] a, int i, int j) {
Comparable temp;
temp = a[i];
a[i] = a[j];
a[j] = temp;
}
}
三.插入排序
插入排序API设计
public class Insertion {
/**
* 对数组a中的元素进行排序
*/
public static void sort(Comparable[] a) {
for (int i = 1; i < a.length; i++) {
for (int j = i; j > 0; j--) {
// 比较索引j处的值和索引j-1处的值,如果索引j-1处的值大,则交换数据,如果不大,
// 那么就找到合适的位置了,退出循环即可
if (greater(a[j - 1], a[j])) {
exch(a, j - 1, j);
} else {
break;
}
}
}
}
/**
* 比较v元素是否大于w元素
*/
private static boolean greater(Comparable v, Comparable w) {
return v.compareTo(w) > 0;
}
/**
* 数组元素i和j交换位置
*/
private static void exch(Comparable[] a, int i, int j) {
Comparable temp;
temp = a[i];
a[i] = a[j];
a[j] = temp;
}
}
4.希尔排序API设计
public class Shell {
/**
* 对数组a中的元素进行排序
*/
public static void sort(Comparable[] a) {
// 1.根据数组a的长度,确定增长量h的初始值
int h = 1;
while (h < a.length / 2) {
h = h * 2 + 1;
}
//2.希尔排序
while (h >= 1) {
//排序
//2.1.找到待插入的元素
for (int i = h; i < a.length; i++) {
//2.2 把待插入的元素插入到有序数列中
for (int j = i; j >= h; j -= h) {
//待插入的元素是a[j],比较a[j]和a[j-h]
if (greater(a[j - h], a[j])) {
//交换元素
exch(a, j - h, j);
} else {
// 待插入元素找到合适的位置,结束循环
break;
}
}
}
}
}
/**
* 比较v元素是否大于w元素
*/
private static boolean greater(Comparable v, Comparable w) {
return v.compareTo(w) > 0;
}
/**
* 数组元素i和j交换位置
*/
private static void exch(Comparable[] a, int i, int j) {
Comparable temp;
temp = a[i];
a[i] = a[j];
a[j] = temp;
}
}
5.n的阶乘
public class TestFactorial {
/**
* n的阶乘
* @param n
* @return
*/
public long factorial(int n) {
if (n == 1) {
return 1;
}
return n * factorial(n - 1);
}
}
6.归并排序API设计
public class Merge {
// 归并所需要的辅助数组
private static Comparable[] assist;
/**
* 比较v元素是否小于w元素
*/
private static boolean less(Comparable v, Comparable w) {
return v.compareTo(w) < 0;
}
/**
* 数组元素i和j交换位置
*/
private static void exch(Comparable[] a, int i, int j) {
Comparable temp;
temp = a[i];
a[i] = a[j];
a[j] = temp;
}
/**
* 对数组a的元素进行排序
*/
public static void sort(Comparable[] a) {
//1.初始化辅助数组assist
assist = new Comparable[a.length];
//2.定义一个lo变量,hi变量,分别记录数组中最小的索引和最大的索引
int lo = 0;
int hi = a.length - 1;
//3.调用sort重载方法完成数组a中,从索引lo到hi的元素的排序
sort(a, lo, hi);
}
/**
* 对数组a中从lo到hi的元素进行排序
*/
private static void sort(Comparable[] a, int lo, int hi) {
// 做安全性校验
if (hi <= lo) {
return;
}
//对lo到hi之间的数据分为两组
int mid = lo + (hi - lo) / 2;
//分别对每一组数据进行排序
sort(a, lo, mid);
sort(a, mid + 1, hi);
//再把两个组中的数据进行归并
merge(a, lo, mid, hi);
}
/**
* 对数组中,从lo到mid为一组,从mid+1到hi为一组,对这两组数据进行归并
*/
private static void merge(Comparable[] a, int lo, int mid, int hi) {
//定义三个数组
int i = lo;
int p1 = lo;
int p2 = mid + 1;
// 遍历,移动p1指针和p2指针,比较对应索引处的值,找到小的那个,放到辅助数组的对应索引处
while (p1 <= mid && p2 <= hi) {
//比较对应索引处的值
if (less(a[p1], a[p2])) {
assist[i++] = a[p1++];
} else {
assist[i++] = a[p2++];
}
}
//遍历,如果p1的指针没有走完,那么顺序移动p1指针,把对应的元素放到辅助数组的对应索引处
while (p1 <= mid) {
assist[i++] = a[p1++];
}
//遍历,如果p2的指针没有走完,那么顺序移动p2指针,把对应的元素放到辅助数组的对应索引处
while (p2 <= hi) {
assist[i++] = a[p2++];
}
//把辅助数组中的元素拷贝到原数组中
for (int index = lo; index <= hi; index++) {
a[index++] = assist[index++];
}
}
}
7.快速排序API设计
public class Quick {
/**
* 比较v元素是否小于w元素
*/
private static boolean less(Comparable v, Comparable w) {
return v.compareTo(w) < 0;
}
/**
* 数组元素i和j交换位置
*/
private static void exch(Comparable[] a, int i, int j) {
Comparable temp;
temp = a[i];
a[i] = a[j];
a[j] = temp;
}
/**
* 对数组a的元素进行排序
*/
public static void sort(Comparable[] a) {
int lo = 0;
int hi = a.length - 1;
sort(a, lo, hi);
}
/**
* 对数组a中从lo到hi的元素进行排序
*/
private static void sort(Comparable[] a, int lo, int hi) {
//安全性校验
if (hi < lo) {
return;
}
//需要对数组中lo索引到hi索引处的元素进行分组(左子组和右子组)
//返回的是分组的分界值所在的索引
int partition = partition(a, lo, hi);
//让左子组有序
sort(a, lo, partition - 1);
//让右子组有序
sort(a, partition + 1, hi);
}
// 对数组a中,从索引lo到索引hi之间的元素进行分组,并返回分组界限对应的索引
public static int partition(Comparable[] a, int lo, int hi) {
// 确定分界值
Comparable key = a[lo];
// 定义两个指针,分别指向待切分元素的最小索引处和最大索引处的下一个位置
int left = lo;
int right = hi + 1;
//切分
while (true) {
//先从右到左扫描,移动right指针,找到一个比分界值小的元素,停止
while (less(key, a[--right])) {
if (right == lo) {
break;
}
}
//再从左往右扫描,移动left指针,找到一个比分界值大的元素,停止
while (less(a[++left], key)) {
if (left == hi) {
break;
}
}
//判断left>=right,如果是,则证明元素扫描完毕,结束循环,如果不是,则交换元素即可
if (left >= right) {
break;
} else {
exch(a, left, right);
}
}
//交换分界值
exch(a, lo, right);
return right;
}
}
8.顺序表API设计
public class SequenceList implements Iterable {
//存储元素的数组
private T[] eles;
//记录当前顺序表的元素个数
private int N;
//构造方法
public SequenceList(int capacity) {
//初始化数组
this.eles = (T[]) new Object[capacity];
//初始化长度
this.N = 0;
}
//将一个线性表置为空表
public void clear() {
this.N = 0;
}
//判断当前线性表是否为空表
public boolean isEmpty() {
return N == 0;
}
//获取线性表的长度
public int length() {
return N;
}
//获取指定位置的元素
public T get(int i) {
return eles[i];
}
//向线性表中添加元素t
public void insert(T t) {
if (N == eles.length) {
resize(2 * eles.length);
}
eles[N++] = t;
}
//向线性表中添加元素t
public void insert(int i, T t) {
if (N == eles.length) {
resize(2 * eles.length);
}
//先把i索引处的元素及其后面的元素依次向后移动一位
for (int index = N; index > i; index--) {
eles[index] = eles[index - 1];
}
//再把t元素放到i索引处即可
eles[i] = t;
//元素个数+1
N++;
}
//删除指定位置i处的元素,并返回该元素
public T remove(int i) {
//记录索引i处的值
T current = eles[i];
//索引i后面元素依次向前移动一位即可
for (int index = i; index < N - 1; index++) {
eles[index] = eles[index + 1];
}
//元素个数-1
N--;
if (N < eles.length / 4) {
resize(eles.length / 2);
}
return current;
}
//查询T元素第一次出现的位置
public int indexOf(T t) {
for (int i = 0; i < N; i++) {
if (eles[i].equals(t)) {
return i;
}
}
return -1;
}
//根据参数newSize,重置eles的大小
public void resize(int newSize) {
//定义一个临时数组,指向原数组
T[] temp = eles;
//创建新数组
eles = (T[]) new Object[newSize];
//把原数组的数据拷贝到新数组即可
for (int i = 0; i < N; i++) {
eles[i] = temp[i];
}
}
@NonNull
@Override
public Iterator iterator() {
return new SIterator();
}
private class SIterator implements Iterator {
private int cursor;
public SIterator() {
this.cursor = 0;
}
@Override
public boolean hasNext() {
return cursor < N;
}
@Override
public Object next() {
return eles[cursor++];
}
}
}
9.单向链表API设计
public class LinkedList implements Iterable {
//记录头结点
private Node head;
//记录链表的长度
private int N;
//结点类
private class Node {
//存储数据
T item;
//下一个结点
Node next;
public Node(T item, Node next) {
this.item = item;
this.next = next;
}
}
public LinkedList() {
//初始化头结点
this.head = new Node(null, null);
//初始化元素个数
this.N = 0;
}
//清空链表
public void clear() {
head.next = null;
this.N = 0;
}
//获取链表的长度
public int length() {
return N;
}
//判断链表是否为空
public boolean isEmpty() {
return N == 0;
}
//获取指定位置i处的元素
public T get(int i) {
//通过循环,从头结点开始往后找,依次找i次,就可以找到对应的元素
Node n = head.next;
for (int index = 0; index < i; index++) {
n = n.next;
}
return n.item;
}
//向链表中添加元素t
public void insert(T t) {
//找到当前最后一个结点
Node n = head;
while (n.next != null) {
n = n.next;
}
//创建新结点,保存元素t
Node newNode = new Node(t, null);
//让当前最后一个结点指向新结点
n.next = newNode;
//元素的个数+1
N++;
}
//向指定位置i处,添加元素t
public void insert(int i, T t) {
//找到i位置前一个结点
Node pre = head;
for (int index = 0; index <= i - 1; index++) {
pre = pre.next;
}
//找到i位置的结点
Node curr = pre.next;
//创建新结点,并且新结点需要指向原来i位置的结点
Node newNode = new Node(t, curr);
//原来i位置的前一个结点指向新结点即可
pre.next = newNode;
//元素的个数+1
N++;
}
//删除指定位置i处的结点,并返回被删除的元素
public T remove(int i) {
//找到i位置前一个结点
Node pre = head;
for (int index = 0; index <= i - 1; i++) {
pre = pre.next;
}
//找到i位置的结点
Node curr = pre.next;
//找到i位置的下一个结点
Node nextNode = curr.next;
//前一个结点指向下一个结点
pre.next = nextNode;
//元素个数-1
N--;
return curr.item;
}
//查找元素t在链表中第一次出现的位置
public int indexOf(T t) {
//从头结点开始,依次找到每一个结点,取出item,和t比较,如果相同,就找到了
Node n = head;
for (int i = 0; n.next != null; i++) {
n = n.next;
if (n.item.equals(t)) {
return i;
}
}
return -1;
}
@NonNull
@Override
public Iterator iterator() {
return new LIterator();
}
private class LIterator implements Iterator {
private Node n;
public LIterator() {
this.n = head;
}
@Override
public boolean hasNext() {
return n.next != null;
}
@Override
public Object next() {
n = n.next;
return n.item;
}
}
//用来反转整个链表
public void reverse(){
//判断当前链表是否为空链表,如果是空链表,则结束运行,如果不是,则调用重载的reverse方法完成反转
if (isEmpty()){
return;
}
reverse(head.next);
}
//反转指定的结点curr,并把反转后的结点返回
public Node reverse(Node curr) {
if (curr.next==null){
head.next=curr;
return curr;
}
//递归的反转当前结点curr的下一个结点,返回值就是链表反转后,当前结点的上一个结点
Node pre=reverse(curr.next);
//让返回的结点的下一个结点变成当前结点curr
pre.next=curr;
//把当前结点的下一个结点变成null
curr.next=null;
return curr;
}
}
10.双向链表API设计
public class TwoWayLinkList implements Iterable {
//首结点
private Node head;
//最后一个结点
private Node last;
//链表的长度
private int N;
//结点类
private class Node {
//存储数据
public T item;
//指向下一个结点
public Node next;
//指向上一个结点
public Node pre;
public Node(T item, Node pre, Node next) {
this.item = item;
this.pre = pre;
this.next = next;
}
}
public TwoWayLinkList() {
//初始化头结点和尾结点
this.head = new Node(null,null,null);
this.last=null;
this.N=0;
}
//清空链表
public void clear(){
this.head.next=null;
this.head.pre=null;
this.head.item=null;
this.last=null;
this.N=0;
}
//获取链表的长度
public int length() {
return N;
}
//判断链表是否为空
public boolean isEmpty() {
return N == 0;
}
//获取第一个元素
public T getFirst(){
if (isEmpty()){
return null;
}
return head.next.item;
}
//获取最后一个元素
public T getLast(){
if (isEmpty()){
return null;
}
return last.item;
}
//插入元素t
public void insert(T t){
if (isEmpty()){
//如果链表为空
//创建新的结点
Node newNode = new Node(t,head,null);
//让新结点成为尾结点
last=newNode;
//让头结点指向尾结点
head.next=last;
}else {
//如果链表不为空
Node oldLast =last;
//创建新的结点
Node newNode = new Node(t,oldLast,null);
//让当前的尾结点指向新结点
oldLast.next=newNode;
//让新结点成为尾结点
last=newNode;
}
//元素个数+1
N++;
}
//向指定位置i处插入元素t
public void insert(int i,T t){
//找到i位置的前一个结点
Node pre=head;
for (int index=0;index iterator() {
return new LIterator();
}
private class LIterator implements Iterator {
private Node n;
public LIterator() {
this.n=head;
}
@Override
public boolean hasNext() {
return n.next!=null;
}
@Override
public Object next() {
n=n.next;
return n.item;
}
}
}
11.快慢指针-中间值问题
public class FastSlowTest {
public static void main(String[] args) {
Node first=new Node<>("aa",null);
Node second=new Node<>("bb",null);
Node third=new Node<>("cc",null);
Node fourth=new Node<>("dd",null);
Node fifth=new Node<>("ee",null);
Node six=new Node<>("ff",null);
Node seven=new Node<>("gg",null);
//完成结点之间的指向
first.next=second;
second.next=third;
third.next=fourth;
fourth.next=fifth;
fifth.next=six;
six.next=seven;
//查找中间值
String mid=getMid(first);
System.out.println("中间值为:"+mid);
}
public static String getMid(Node first){
//定义两个指针
Node fast=first;
Node slow=first;
//使用两个指针遍历链表,当快指针指向的结点没有下一个结点了,就可以结束了,结束之后,慢指针指向的结点就是中间值
while (fast!=null && fast.next!=null){
//变化fast的值和slow的值
fast=fast.next.next;
slow=slow.next;
}
return slow.item;
}
//结点类
private static class Node{
//存储数据
T item;
//下一个结点
Node next;
public Node(T item,Node next){
this.item=item;
this.next=next;
}
}
}
12.单链表是否有环问题
public class CircleListCheckTest {
public static void main(String[] args) {
Node first=new Node<>("aa",null);
Node second=new Node<>("bb",null);
Node third=new Node<>("cc",null);
Node fourth=new Node<>("dd",null);
Node fifth=new Node<>("ee",null);
Node six=new Node<>("ff",null);
Node seven=new Node<>("gg",null);
//完成结点之间的指向
first.next=second;
second.next=third;
third.next=fourth;
fourth.next=fifth;
fifth.next=six;
six.next=seven;
//生产环
seven.next=third;
//查找中间值
boolean circle=isCircle(first);
System.out.println("first链表中是否有环:"+circle);
}
//判断链表中是否有环
public static boolean isCircle(Node first) {
//定义两个指针
Node fast=first;
Node slow=first;
//遍历链表,如果快慢指针指向了同一个结点,那么证明有环
while (fast!=null && fast.next!=null){
//变化fast的值和slow的值
fast=fast.next.next;
slow=slow.next;
if (fast.equals(slow)){
return true;
}
}
return false;
}
//结点类
private static class Node{
//存储数据
T item;
//下一个结点
Node next;
public Node(T item,Node next){
this.item=item;
this.next=next;
}
}
}
13.有环链表入口问题
public class CircleListInTest {
public static void main(String[] args) {
Node first=new Node<>("aa",null);
Node second=new Node<>("bb",null);
Node third=new Node<>("cc",null);
Node fourth=new Node<>("dd",null);
Node fifth=new Node<>("ee",null);
Node six=new Node<>("ff",null);
Node seven=new Node<>("gg",null);
//完成结点之间的指向
first.next=second;
second.next=third;
third.next=fourth;
fourth.next=fifth;
fifth.next=six;
six.next=seven;
//生产环
seven.next=third;
//查找环的入口结点
Node entrance=getEntrance(first);
System.out.println("first链表中环的入口结点元素为:"+entrance.item);
}
//判断链表中是否有环
public static Node getEntrance(Node first) {
//定义快慢指针
Node fast=first;
Node slow=first;
Node temp=null;
//遍历链表,先找到环(快慢指针相遇),准备一个临时指针,指向链表的首结点,继续遍历,直到慢指针和临时指针相遇,
//那么相遇时所指向的结点就是环的入口
while (fast!=null && fast.next!=null){
//变换快慢指针
fast=fast.next.next;
slow=slow.next;
//判断快慢指针是否相遇
if (fast.equals(slow)){
temp=first;
continue;
}
//让临时结点变换
if (temp!=null){
temp=temp.next;
//判断临时指针是否和慢指针相遇
if (temp.equals(slow)){
break;
}
}
}
return temp;
}
//结点类
private static class Node{
//存储数据
T item;
//下一个结点
Node next;
public Node(T item,Node next){
this.item=item;
this.next=next;
}
}
}
14.约瑟夫问题
public class JosephTest {
public static void main(String[] args) {
//1.构建循环链表,包含41个结点,分别存储1-41之间的值
//首结点
Node first = null;
//用来记录前一个结点
Node pre = null;
for (int i = 0; i <= 41; i++) {
//如果是第一个结点
if (i == 1) {
first = new Node<>(i, null);
pre = first;
continue;
}
//如果不是第一个结点
Node newNode = new Node<>(i, null);
pre.next = newNode;
pre = newNode;
//如果是最后一个结点,那么需要让最后一个结点的下一个结点变成first,变成循环链表了
if (i == 41) {
pre.next = first;
}
}
//2.需要count计数器,模拟报数
int count = 0;
//3.遍历循环链表
//记录每次遍历拿到的结点,默认从首结点开始
Node n = first;
//记录当前结点的上一个结点
Node before = null;
while (n != n.next) {
//模拟报数
count++;
//判断当前报数是不是为3
if (count == 3) {
//如果是3,则把当前结点删除调,打印当前节点,重置count=0,让当前结点n后移
before.next = n.next;
System.out.print(n.item + ",");
n = n.next;
} else {
//如果不是3,让before变成当前结点,让当前结点后移
before = n;
n = n.next;
}
}
//打印最后一个结点
System.out.println(n.item);
}
//结点类
private static class Node {
//存储数据
T item;
//下一个结点
Node next;
public Node(T item, Node next) {
this.item = item;
this.next = next;
}
}
}
15.栈的API设计
public class Stack implements Iterable {
//记录头结点
private Node head;
//记录链表的长度
private int N;
//结点类
private class Node {
//存储数据
T item;
//下一个结点
Node next;
public Node(T item, Node next) {
this.item = item;
this.next = next;
}
}
public Stack() {
this.head = new Node(null, null);
this.N = 0;
}
//判断当前栈中元素个数是否为0
public boolean isEmpty() {
return N == 0;
}
//获取栈中元素的个数
public int size() {
return N;
}
//把t元素压入栈
public void push(T t) {
//找到首结点指向的第一个结点
Node oldFirst = head.next;
//创建新结点
Node newNode = new Node(t, null);
//让首结点指向新结点
head.next = newNode;
//新结点指向原来的第一个结点
newNode.next = oldFirst;
//元素个数+1
N++;
}
//弹出栈顶元素
public T pop() {
//找到首结点指向的第一个结点
Node oldFirst = head.next;
if (oldFirst == null) {
return null;
}
//让首结点指向原来第一个结点的下一个结点
head.next = oldFirst.next;
//元素个数-1
N--;
return oldFirst.item;
}
@NonNull
@Override
public Iterator iterator() {
return new LIterator();
}
private class LIterator implements Iterator {
private Node n;
public LIterator() {
this.n = head;
}
@Override
public boolean hasNext() {
return n.next != null;
}
@Override
public Object next() {
n = n.next;
return n.item;
}
}
}
16.栈案例括号匹配问题
public class BracketsMatchTest {
public static void main(String[] args) {
String str = "(上海(长安)())";
boolean match = isMatch(str);
System.out.println(str + "中的括号是否匹配" + match);
}
//判断str中的括号是否匹配
public static boolean isMatch(String str) {
//1.创建栈对象,用来存储左括号
Stack chars = new Stack<>();
//2.从左往右遍历字符串
for (int i = 0; i < str.length(); i++) {
String currChar = str.charAt(i) + "";
//3.判断当前是否为左括号,如果是,则把字符放入栈中
if (currChar.equals("(")) {
chars.push(currChar);
} else if (currChar.equals(")")) {
//4.继续判断当前字符是否为右括号,如果是,则从栈中弹出一个左括号,并判断弹出的结果是否为null,如果为null
//证明没有匹配的左括号
String pop = chars.pop();
if (pop == null) {
return false;
}
}
}
//判断栈中还有没有剩余的左括号,如果有,则证明左括号不匹配
if (chars.size() == 0) {
return true;
} else {
return false;
}
}
}
17.栈案例逆波兰表达式求值问题
public class ReversePolishNotationTest {
public static void main(String[] args) {
String[] notation = {"3", "17", "15", "-", "*", "18", "6", "/", "+"};
int result = caculate(notation);
System.out.println("逆波兰表达式的结果为:" + result);
}
public static int caculate(String[] notation) {
//1.定义一个栈,用来存储操作数
Stack oprands = new Stack<>();
//2.从左往右遍历逆波兰表达式,得到每一个元素
for (int i = 0; i < notation.length; i++) {
String curr = notation[i];
//3.判断当前元素是运算符还是操作数
Integer o1;
Integer o2;
Integer result;
switch (curr) {
case "+":
//4.运算符,从栈中弹出两个操作数,完成运算,运算完的结果再压入栈中
o1 = oprands.pop();
o2 = oprands.pop();
result = o2 + o1;
oprands.push(result);
break;
case "-":
//4.运算符,从栈中弹出两个操作数,完成运算,运算完的结果再压入栈中
o1 = oprands.pop();
o2 = oprands.pop();
result = o2 - o1;
oprands.push(result);
break;
case "*":
//4.运算符,从栈中弹出两个操作数,完成运算,运算完的结果再压入栈中
o1 = oprands.pop();
o2 = oprands.pop();
result = o2 * o1;
oprands.push(result);
break;
case "/":
//4.运算符,从栈中弹出两个操作数,完成运算,运算完的结果再压入栈中
o1 = oprands.pop();
o2 = oprands.pop();
result = o2 / o1;
oprands.push(result);
break;
default:
//5.操作数,把该操作数放入栈中
oprands.push(Integer.parseInt(curr));
break;
}
}
//6.得到栈中最后一个元素,就是逆波兰表达式的结果
int result = oprands.pop();
return result;
}
}
18.队列API设计
public class Queue implements Iterable {
//记录头结点
private Node head;
//记录最后一个结点
private Node last;
//记录队列中元素的个数
private int N;
//结点类
private class Node {
//存储数据
T item;
//下一个结点
Node next;
public Node(T item, Node next) {
this.item = item;
this.next = next;
}
}
public Queue() {
this.head = new Node(null, null);
this.last = null;
this.N = 0;
}
//获取队列中元素的个数
public int length() {
return N;
}
//判断队列是否为空
public boolean isEmpty() {
return N == 0;
}
//向队列中插入元素t
public void enqueue(T t) {
if (last == null) {
//当前尾结点last为null
last = new Node(t, null);
head.next = last;
} else {
//当前尾结点last不为null
Node oldLast = last;
last = new Node(t, null);
oldLast.next = last;
}
//元素个数+1
N++;
}
//从队列中拿出一个元素
public T dequeue() {
if (isEmpty()) {
return null;
}
Node oldFirst = head.next;
head.next = oldFirst.next;
N--;
//因为队列其实是在删除元素,因此如果队列中的元素被删除完了,需要重置last=null
if (isEmpty()) {
last = null;
}
return oldFirst.item;
}
@NonNull
@Override
public Iterator iterator() {
return new QIterator();
}
private class QIterator implements Iterator {
private Node n;
public QIterator() {
this.n = head;
}
@Override
public boolean hasNext() {
return n.next != null;
}
@Override
public Object next() {
n = n.next;
return n.item;
}
}
}
19.符号表API设计
public class SymbolTable {
//记录首结点
private Node head;
//记录符号表中元素的个数
private int N;
private class Node {
//键
public Key key;
//值
public Value value;
//下一个结点
public Node next;
public Node(Key key, Value value, Node next) {
this.key = key;
this.value = value;
this.next = next;
}
}
public SymbolTable() {
this.head = new Node(null, null, null);
this.N = 0;
}
// 获取符号表中键值对的个数
public int size() {
return N;
}
//往符号表中插入键值对
public void put(Key key, Value value) {
//符号表中已经存在了键为key的键值对,那么只需要找到该结点,替换值为value即可
Node n = head;
while (n.next != null) {
//变换n
n = n.next;
//判断n结点存储的键是否为key,如果是,则替换n结点的值
if (n.key.equals(key)) {
n.value = value;
return;
}
}
//如果符号表中不存在键为key的键值对,只需要创建新的结点,保存要插入的键值对,把新结点插入到链表的头部,head.next=新结点即可
Node newNode = new Node(key, value, null);
Node oldFirst = head.next;
newNode.next = oldFirst;
head.next = newNode;
//元素个数+1
N++;
}
// 删除符号表中键为key的键值对
public void delete(Key key) {
//找到键为key的结点,把该结点从链表中删除
Node n = head;
while (n.next != null) {
//判断n结点的下一个结点的键是否为key,如果是,就删除该结点
if (n.next.key.equals(key)) {
n.next = n.next.next;
N--;
return;
}
//变换n
n = n.next;
}
}
// 从符号表中获取key对应的值
public Value get(Key key) {
//找到键为key的结点
Node n = head;
while (n.next != null) {
//变换n
n = n.next;
if (n.key.equals(key)) {
return n.value;
}
}
return null;
}
}
20.有序符号表API设计
public class OrderSymbolTable {
//记录首结点
private Node head;
//记录符号表中元素的个数
private int N;
private class Node {
//键
public Key key;
//值
public Value value;
//下一个结点
public Node next;
public Node(Key key, Value value, Node next) {
this.key = key;
this.value = value;
this.next = next;
}
}
public OrderSymbolTable() {
this.head = new Node(null, null, null);
this.N = 0;
}
// 获取符号表中键值对的个数
public int size() {
return N;
}
//往符号表中插入键值对
public void put(Key key, Value value) {
// 定义两个Node变量,分别记录当前结点和当前结点的上一个结点
Node curr = head.next;
Node pre = head;
while (curr != null && key.compareTo(curr.key) > 0) {
//变换上一个结点和当前结点
pre = curr;
curr = curr.next;
}
//如果当前结点curr的键和要插入的key一样,则替换
if (curr != null && key.compareTo(curr.key) == 0) {
curr.value = value;
return;
}
//如果当前结点curr的键和要插入的key不一样,把新的结点插入到curr之前
Node newNode = new Node(key, value, curr);
pre.next = newNode;
//元素个数+1
N++;
}
// 删除符号表中键为key的键值对
public void delete(Key key) {
//找到键为key的结点,把该结点从链表中删除
Node n = head;
while (n.next != null) {
//判断n结点的下一个结点的键是否为key,如果是,就删除该结点
if (n.next.key.equals(key)) {
n.next = n.next.next;
N--;
return;
}
//变换n
n = n.next;
}
}
// 从符号表中获取key对应的值
public Value get(Key key) {
//找到键为key的结点
Node n = head;
while (n.next != null) {
//变换n
n = n.next;
if (n.key.equals(key)) {
return n.value;
}
}
return null;
}
}
21.二叉树API设计
public class BinaryTree, Value> {
// 记录根结点
private Node root;
// 记录树中元素的个数
private int N;
private class Node {
//存储键
public Key key;
//存储值
public Value value;
//记录左子结点
public Node left;
//记录右子结点
public Node right;
public Node(Key key, Value value, Node left, Node right) {
this.key = key;
this.value = value;
this.left = left;
this.right = right;
}
}
//获取元素个数
public int size() {
return N;
}
//向树中添加元素key-value
public void put(Key key, Value value) {
root = put(root, key, value);
}
//向指定的树x中添加key-value,并返回添加元素后新的树
private Node put(Node x, Key key, Value value) {
//如果x子树为空
if (x == null) {
N++;
return new Node(key, value, null, null);
}
//如果x子树不为空
//比较x结点的键和key的大小
int cmp = key.compareTo(x.key);
if (cmp > 0) {
//如果key大于x结点的键,则继续找x结点的右子树
x.right = put(x.right, key, value);
} else if (cmp < 0) {
//如果key小于x结点的键,则继续找x结点的左子树
x.left = put(x.left, key, value);
} else {
//如果key等于x结点的键,则替换x结点的值为value即可
x.value = value;
}
return x;
}
// 查询树中指定key对应的value
public Value get(Key key) {
return get(root, key);
}
// 从指定的树x中,查找key对应的值
public Value get(Node x, Key key) {
//x树为null
if (x == null) {
return null;
}
//x树不为null
//比较key和x结点的键的大小
int cmp = key.compareTo(x.key);
if (cmp > 0) {
//如果key大于x结点的键,则继续找x结点的右子树
return get(x.right, key);
} else if (cmp < 0) {
//如果key小于x结点的键,则继续找x结点的左子树
return get(x.left, key);
} else {
//如果key等于x结点的键,就找到了键为key的结点,只需要返回x结点的值即可
return x.value;
}
}
//删除树中key对应的value
public void delete(Key key) {
delete(root, key);
}
// 删除指定树x中的key对应的value,并返回删除后的新树
private Node delete(Node x, Key key) {
//x树为null
if (x == null) {
return null;
}
//x树不为null
int cmp = key.compareTo(x.key);
if (cmp > 0) {
//如果key大于x结点的键,则继续找x结点的右子树
x.right = delete(x.right, key);
} else if (cmp < 0) {
//如果key小于x结点的键,则继续找x结点的左子树
x.left = delete(x.left, key);
} else {
//如果key等于x结点的键,完成真正地删除结点动作,要删除的结点就是x
//让元素个数-1
N--;
//得找到右子树中最小的结点
if (x.right == null) {
return x.left;
}
if (x.left == null) {
return x.right;
}
Node minNode = x.right;
while (minNode.left != null) {
minNode = minNode.left;
}
//删除右子树中最小的结点
Node n = x.right;
while (n.left != null) {
if (n.left.left == null) {
n.left = null;
} else {
//变换n结点即可
n = n.left;
}
}
//让x结点的左子树成为minNode的左子树
minNode.left = x.left;
//让x结点的右子树成为minNode的右子树
minNode.right = x.right;
//让x结点的父节点指向minNode
x = minNode;
}
return x;
}
//查找整个树中最小的键
public Key min() {
return min(root).key;
}
//在指定树x中找到最小键所在的结点
private Node min(Node x) {
//需要判断x还有没有左子结点,如果有,则继续向左找,如果没有,则x就是最小键所在的结点
if (x.left != null) {
return min(x.left);
} else {
return x;
}
}
//查找整个树中找到最大的键
public Key max() {
return max(root).key;
}
//在指定树x中找到最大键所在的结点
private Node max(Node x) {
//需要判断x还有没有右子结点,如果有,则继续向右找,如果没有,则x就是最大键所在的结点
if (x.right != null) {
return max(x.right);
} else {
return x;
}
}
// 获取整个树中所有的键
public Queue preErgodic() {
Queue keys = new Queue<>();
preErgodic(root, keys);
return keys;
}
// 获取指定树x的所有键,并放在keys队列中
private void preErgodic(Node x, Queue keys) {
if (x == null) {
return;
}
//把x结点的key放入到keys中
keys.enqueue(x.key);
//递归遍历x结点的左子树
if (x.left != null) {
preErgodic(x.left, keys);
}
//递归遍历x结点的右子树
if (x.right != null) {
preErgodic(x.right, keys);
}
}
// 使用中序遍历获取整个树中所有的键
public Queue midErgodic() {
Queue keys = new Queue<>();
preErgodic(root, keys);
return keys;
}
// 使用中序遍历,获取指定树x的所有的键,并放在keys队列中
private void midErgodic(Node x, Queue keys) {
if (x == null) {
return;
}
//先递归,把左子树中的键放到keys中
if (x.left != null) {
midErgodic(x.left, keys);
}
//把当前结点x的键放到keys中
keys.enqueue(x.key);
//再递归,把右子树中的键放到keys中
if (x.right != null) {
midErgodic(x.right, keys);
}
}
// 使用后序遍历获取整个树中所有的键
public Queue afterErgodic() {
Queue keys = new Queue<>();
afterErgodic(root, keys);
return keys;
}
// 使用后序遍历,获取指定树x的所有的键,并放在keys队列中
private void afterErgodic(Node x, Queue keys) {
if (x == null) {
return;
}
//先递归,把左子树中的键放到keys中
if (x.left != null) {
afterErgodic(x.left, keys);
}
//再递归,把右子树中的键放到keys中
if (x.right != null) {
afterErgodic(x.right, keys);
}
//把当前结点x的键放到keys中
keys.enqueue(x.key);
}
// 使用层序遍历,获取整个树中所有的键
public Queue layerErgodic() {
//定义两个队列,分别存储树中的键和树中的结点
Queue keys = new Queue<>();
Queue nodes = new Queue<>();
//默认,往队列中放入跟结点
nodes.enqueue(root);
while (!nodes.isEmpty()) {
//从队列中弹出一个结点,把key放入到keys中
Node n = nodes.dequeue();
keys.enqueue(n.key);
//判断当前结点还有没有左子结点,如果有,则放入到nodes中
if (n.left != null) {
nodes.enqueue(n.left);
}
//判断当前结点还有没有右子结点,如果有,则放入到nodes中
if (n.right != null) {
nodes.enqueue(n.right);
}
}
return keys;
}
//获取整个树的最大深度
public int maxDepth() {
return maxDepth(root);
}
//获取指定树x的最大深度
private int maxDepth(Node x) {
if (x == null) {
return 0;
}
//x的最大深度
int max = 0;
//左子树的最大深度
int maxL = 0;
//右子树的最大深度
int maxR = 0;
//计算x结点左子树的最大深度
if (x.left != null) {
maxL = maxDepth(x.left);
}
//计算x结点右子树的最大深度
if (x.right != null) {
maxR = maxDepth(x.right);
}
//比较左子树最大深度和右子树最大深度,取较大值+1即可
max = maxL > maxR ? maxL + 1 : maxR + 1;
return max;
}
}
22.二叉树折纸问题
public class PagerFoldingTest {
public static void main(String[] args) {
//模拟折纸过程,产生树
Node tree = createTree(2);
//遍历树,打印每个结点
printTree(tree);
}
// 通过模拟对折N次纸,产生树
public static Node createTree(int N) {
//定义根节点
Node root = null;
for (int i = 0; i < N; i++) {
//1.当前是第一次对折
if (i == 0) {
root = new Node<>("down", null, null);
continue;
}
//2.当前不是第一次对折
//定义一个辅助队列,通过层序遍历的思想,找到叶子结点,叶子结点添加子结点
Queue queue = new Queue<>();
queue.enqueue(root);
//循环遍历队列
while (!queue.isEmpty()) {
//从队列中弹出一个结点
Node tmp = queue.dequeue();
//如果有左子结点,则把左子结点放入到队列中
if (tmp.left != null) {
queue.enqueue(tmp.left);
}
//如果有右子结点,则把右子结点放入到队列中
if (tmp.right != null) {
queue.enqueue(tmp.right);
}
//如果同时没有左子结点和右子结点,那么证明该结点是叶子结点,只需要给该结点添加左子结点和右子结点即可
if (tmp.left == null && tmp.right == null) {
tmp.left = new Node("down", null, null);
tmp.right = new Node("up", null, null);
}
}
}
return root;
}
// 打印树中每个结点到控制台
public static void printTree(Node root) {
//需要使用中序遍历完成
if (root == null) {
return;
}
//打印左子树的每个结点
if (root.left != null) {
printTree(root.left);
}
//打印当前结点
System.out.print(root.item + " ");
//打印右子树的每个结点
if (root.right != null) {
printTree(root.right);
}
}
private static class Node {
public T item;//存储数据
public Node left;
public Node right;
public Node(T item, Node left, Node right) {
this.item = item;
this.left = left;
this.right = right;
}
}
}
23.堆API设计
public class Heap> {
//存储堆中的元素
private T[] items;
//记录堆中元素的个数
private int N;
public Heap(int capacity) {
this.items = (T[]) new Comparable[capacity + 1];
this.N = 0;
}
// 判断堆中索引i处的元素是否小于索引j处的元素
private boolean less(int i, int j) {
return items[i].compareTo(items[j]) < 0;
}
// 交换堆中i索引和j索引处的值
private void exch(int i, int j) {
T temp = items[i];
items[i] = items[j];
items[j] = temp;
}
//往堆中插入一个元素
public void insert(T t) {
items[++N] = t;
swim(N);
}
//使用上浮算法,使索引k处的元素能在堆中处于一个正确的位置
private void swim(int k) {
//通过循环,不断的比较当前结点的值和其父结点的值,如果发现父结点的值比当前结点的值小,则交换位置
while (k > 1) {
//比较当前结点和其父结点
if (less(k / 2, k)) {
exch(k / 2, k);
}
k = k / 2;
}
}
// 删除堆中最大的元素,并返回这个最大元素
public T delMax() {
T max = items[1];
//交换索引1处的元素和最大索引处的元素,让完全二叉树中最右侧的元素变为临时根节点
exch(1, N);
//最大索引处的元素删除掉
items[N] = null;
//元素个数-1
N--;
//通过下沉调整堆,让堆重新有序
sink(1);
return max;
}
//使用下沉算法,使索引k处的元素能在堆中处于一个正确的位置
private void sink(int k) {
//通过循环不断地对比当前k结点和其左子结点2*k以及右子结点2k+1中的较大值的元素大小,如果
//当前结点小,则需要交换位置
while (2 * k < N) {
//获取当前结点的子结点中的较大结点
int max;//记录较大结点所在的索引
if (2 * k + 1 <= N) {
if (less(2 * k, 2 * k + 1)) {
max = 2 * k + 1;
} else {
max = 2 * k;
}
} else {
max = 2 * k;
}
//比较当前结点和较大结点的值
if (!less(k, max)) {
break;
}
//交换k索引处的值和max索引处的值
exch(k, max);
// 变换k的值
k = max;
}
}
}
24.堆排序
public class HeapSort {
//判断heap堆中索引i处的元素是否小于索引j处的元素
private static boolean less(Comparable[] heap, int i, int j) {
return heap[i].compareTo(heap[j]) < 0;
}
//交换heap堆中i索引和j索引处的值
private static void exch(Comparable[] heap, int i, int j) {
Comparable tmp = heap[i];
heap[i] = heap[j];
heap[j] = tmp;
}
// 根据原数组source,构造出堆heap
private static void createHeap(Comparable[] source, Comparable[] heap) {
//把source中的元素拷贝到heap中,heap中的元素就形成了一个无序的堆
System.arraycopy(source, 0, heap, 1, source.length);
//对堆中的元素做下沉调整(从长度一半处开始,往索引1处扫描)
for (int i = (heap.length / 2); i > 0; i--) {
sink(heap, i, heap.length - 1);
}
}
//对source数组中的数据从小到大排序
public static void sort(Comparable[] source) {
//构建堆
Comparable[] heap = new Comparable[source.length + 1];
createHeap(source, heap);
//定义一个变量,记录未排序的元素中最大的索引
int N = heap.length - 1;
//通过循环,交换1索引处的元素和未排序的元素中最大的索引处的元素
while (N != 1) {
//交换元素
exch(heap, 1, N);
//未排序交换后最大元素所在的索引,让它不要参与堆的下沉调整
N--;
//需要对索引1处的元素进行下沉调整
sink(heap, 1, N);
}
//把heap中的数据复制到原数组source中
System.arraycopy(heap, 1, source, 0, source.length);
}
// 在heap堆中,对target处的元素做下沉,范围是0~range
private static void sink(Comparable[] heap, int target, int range) {
while (2 * target <= range) {
//1.找出当前结点较大的子结点
int max;
if (2 * target + 1 <= range) {
if (less(heap, 2 * target, 2 * target + 1)) {
max = 2 * target + 1;
} else {
max = 2 * target;
}
} else {
max = 2 * target;
}
//2.比较当前结点的值和较大子结点的值
if (!less(heap, target, max)) {
break;
}
exch(heap, target, max);
target = max;
}
}
}
25.最大优先队列
public class MaxPriorityQueue> {
//存储堆中的元素
private T[] items;
//记录堆中元素个数
private int N;
public MaxPriorityQueue(int capacity) {
this.items = (T[]) new Comparable[capacity + 1];
this.N = 0;
}
//获取队列中元素个数
public int size() {
return N;
}
//判断队列是否为空
public boolean isEmpty() {
return N == 0;
}
//判断堆中索引i处的元素是否小于索引j处的元素
private boolean less(int i, int j) {
return items[i].compareTo(items[j]) < 0;
}
//交换堆中i索引和j索引处的值
private void exch(int i, int j) {
T temp = items[i];
items[i] = items[j];
items[j] = temp;
}
//往堆中插入一个元素
public void insert(T t) {
items[++N] = t;
swim(N);
}
//删除堆中最大的元素,并返回这个最大元素
public T delMax() {
T max = items[1];
//交换索引1处的元素和最大索引处的元素,让完全二叉树中最右侧的元素变为临时根节点
exch(1, N);
//最大索引处的元素删除掉
items[N] = null;
//元素个数-1
N--;
//通过下沉调整堆,让堆重新有序
sink(1);
return max;
}
//使用上浮算法,使索引k处的元素能在堆中处于一个正确的位置
private void swim(int k) {
while (k > 1) {
//比较当前结点和其父结点
if (less(k / 2, k)) {
exch(k / 2, k);
}
k = k / 2;
}
}
//使用下浮算法,使索引k处的元素能在堆中处于一个正确的位置
private void sink(int k){
while (2 * k < N) {
//获取当前结点的子结点中的较大结点
int max;//记录较大结点所在的索引
if (2 * k + 1 <= N) {
if (less(2 * k, 2 * k + 1)) {
max = 2 * k + 1;
} else {
max = 2 * k;
}
} else {
max = 2 * k;
}
//比较当前结点和较大结点的值
if (!less(k, max)) {
break;
}
//交换k索引处的值和max索引处的值
exch(k, max);
// 变换k的值
k = max;
}
}
}
26.最小优先队列
public class MinPriorityQueue> {
//存储堆中的元素
private T[] items;
//记录堆中元素个数
private int N;
public MinPriorityQueue(int capacity) {
this.items = (T[]) new Comparable[capacity + 1];
this.N = 0;
}
//获取队列中元素个数
public int size() {
return N;
}
//判断队列是否为空
public boolean isEmpty() {
return N == 0;
}
//判断堆中索引i处的元素是否小于索引j处的元素
private boolean less(int i, int j) {
return items[i].compareTo(items[j]) < 0;
}
//交换堆中i索引和j索引处的值
private void exch(int i, int j) {
T temp = items[i];
items[i] = items[j];
items[j] = temp;
}
//往堆中插入一个元素
public void insert(T t) {
items[++N] = t;
swim(N);
}
//删除堆中最小的元素,并返回这个最小元素
public T delMin() {
T min = items[1];
exch(1, N);
N--;
sink(1);
return min;
}
//使用上浮算法,使索引k处的元素能在堆中处于一个正确的位置
private void swim(int k) {
//通过循环比较当前结点和其父结点的大小
while (k > 1) {
if (less(k, k / 2)) {
exch(k, k / 2);
}
k = k / 2;
}
}
//使用下浮算法,使索引k处的元素能在堆中处于一个正确的位置
private void sink(int k) {
//通过循环比较当前结点和其子结点中的较小值
while (2 * k <= N) {
//1.找到子结点的较小值
int min;
if (2 * k + 1 <= N) {
if (less(2 * k, 2 * k + 1)) {
min = 2 * k;
} else {
min = 2 * k + 1;
}
} else {
min = 2 * k;
}
//2.判断当前结点和较小值的大小
if (less(k, min)) {
break;
}
exch(k, min);
k = min;
}
}
}
27.索引优先队列
public class IndexMinPriorityQueue> {
//存储堆中的元素
private T[] items;
//保存每个元素在items数组中的索引,pa数组需要堆有序
private int[] pq;
//保存qp的逆序,pq的值作为索引,pq的索引作为值
private int[] qp;
//记录堆中元素个数
private int N;
public IndexMinPriorityQueue(int capacity) {
this.items = (T[]) new Comparable[capacity + 1];
this.pq = new int[capacity + 1];
this.qp = new int[capacity + 1];
this.N = 0;
//默认情况下,队列中没有存储任何数据,让qp中的元素都为-1
for (int i = 0; i < qp.length; i++) {
qp[i] = -1;
}
}
//获取队列中元素个数
public int size() {
return N;
}
//判断队列是否为空
public boolean isEmpty() {
return N == 0;
}
//判断堆中索引i处的元素是否小于索引j处的元素
private boolean less(int i, int j) {
return items[pq[i]].compareTo(items[pq[j]]) < 0;
}
//交换堆中i索引和j索引处的值
private void exch(int i, int j) {
//交换pq中的数据
int tmp = pq[i];
pq[i] = pq[j];
pq[j] = tmp;
//交换qp中的数据
qp[pq[i]] = i;
qp[pq[j]] = j;
}
//判断k对应的元素是否存在
public boolean contains(int k) {
return qp[k] != -1;
}
//最小元素关联的索引
public int minIndex() {
return pq[1];
}
//往队列中插入一个元素,并关联索引i
public void insert(int i, T t) {
// 判断i是否已经被关联,如果已经被关联,则不让插入
if (contains(i)) {
return;
}
//元素个数+1
N++;
//把数据存储到items对应的i位置处
items[i] = t;
//把i存储到pq中
pq[N] = i;
//通过qp记录pq中的i
qp[i] = N;
//通过堆上浮完成堆的调整
swim(N);
}
//删除堆中最小的元素,并返回该元素关联的索引
public int delMin() {
//获取最小元素关联的索引
int minIndex = pq[1];
//交换pq中索引1处和最大索引处的元素
exch(1, N);
//删除qp中对应的内容
qp[pq[N]] = -1;
//删除pq最大索引处的内容
pq[N] = -1;
//删除items中对应的内容
items[minIndex] = null;
//元素个数-1
N--;
//下沉调整
sink(1);
return minIndex;
}
//删除索引i关联的元素
public void delete(int i) {
//找到i在pq中的索引
int k = qp[i];
//交换pq中索引k处的值和索引N处的值
exch(k, N);
//删除qp中的内容
qp[pq[N]] = -1;
//删除pq中的内容
pq[N] = -1;
//删除items中的内容
items[k] = null;
//元素个数-1
N--;
//堆的调整
sink(k);
swim(k);
}
//把与索引i关联的元素修改为t
public void changeItem(int i, T t) {
//修改items数组中i位置的元素t
items[i] = t;
//找到i在pq中出现的位置
int k = qp[i];
//堆调整
sink(k);
swim(k);
}
//使用上浮算法,使索引k处的元素能在堆中处于一个正确的位置
private void swim(int k) {
while (k > 1) {
if (less(k, k / 2)) {
exch(k, k / 2);
}
k = k / 2;
}
}
//使用下浮算法,使索引k处的元素能在堆中处于一个正确的位置
private void sink(int k) {
while (2 * k <= N) {
//找到子结点中的较小值
int min;
if (2 * k + 1 <= N) {
if (less(2 * k, 2 * k + 1)) {
min = 2 * k;
} else {
min = 2 * k + 1;
}
} else {
min = 2 * k;
}
//比较当前结点和较小值
if (less(k, min)) {
break;
}
exch(k, min);
k = min;
}
}
}
28.红黑树API设计
public class RedBlackTree, Value> {
//根节点
private Node root;
//记录树中元素个数
private int N;
//红色链接
private static final boolean RED = true;
//黑色链接
private static final boolean BLACK = false;
//结点类
private class Node {
//存储键
public Key key;
//存储值
public Value value;
//记录左子结点
public Node left;
//记录右子结点
public Node right;
//由其父结点指向它的链接的颜色
public boolean color;
public Node(Key key, Value value, Node left, Node right, boolean color) {
this.key = key;
this.value = value;
this.left = left;
this.right = right;
this.color = color;
}
}
//获取树中元素个数
public int size() {
return N;
}
//判断当前结点的父指向链接是否为红色
private boolean isRed(Node x) {
if (x == null) {
return false;
}
return x.color == RED;
}
//左旋
private Node rotateLeft(Node h) {
//获取h结点的右子结点,表示为x
Node x = h.right;
//让x结点的左子结点成为h结点的右子结点
h.right = x.left;
//让h成为x结点的左子结点
x.left = h;
//让x结点的color属性等于h结点的color属性
x.color = h.color;
//让h结点的color属性变成红色
h.color = RED;
return x;
}
//右旋
private Node rotateRight(Node h) {
//获取h结点的左子结点,表示为x
Node x = h.left;
//让x结点的右子结点成为h结点的左子结点
h.left = x.right;
//让h成为x结点的左子结点
x.right = h;
//让x结点的color属性等于h结点的color属性
x.color = h.color;
//让h结点的color属性变成红色
h.color = RED;
return x;
}
//颜色反转,相当于完成拆分4-结点
private void flipColors(Node h) {
//当前结点变成红色
h.color = RED;
//左子结点和右子结点变为黑色
h.left.color = BLACK;
h.right.color = BLACK;
}
//在整个树上完成操作
public void put(Key key, Value val) {
put(root, key, val);
//根结点的颜色总是黑色
root.color = BLACK;
}
//在指定树中,完成插入操作,并返回添加元素后新的树
public Node put(Node h, Key key, Value val) {
//判断h是否为为空,如果为空则直接返回一个红色的结点就可以了
if (h == null) {
//数量+1
N++;
return new Node(key, val, null, null, RED);
}
//比较h结点的键和key的大小
int cmp = key.compareTo(h.key);
if (cmp < 0) {
//继续往左
h.left = put(h.left, key, val);
} else if (cmp > 0) {
//继续往右
h.right = put(h.right, key, val);
} else {
//发送值的替换
h.value = val;
}
//进行左旋,当当前结点h的左子结点为黑色,右子结点为红色,需要左旋
if (isRed(h.right) && !isRed(h.left)) {
h = rotateLeft(h);
}
//进行右旋,当当前结点h的左子结点和左子结点的左子结点都为红色,需要右旋
if (isRed(h.left) && isRed(h.left.left)) {
h = rotateRight(h);
}
//颜色反转,当前结点的左子结点和右子结点都为红色时,需要颜色反转
if (isRed(h.left) && isRed(h.right)) {
flipColors(h);
}
return h;
}
//根据key,从树中找出对应的值
public Value get(Key key) {
return get(root, key);
}
//从指定的树x中,查找key对应的值
public Value get(Node x, Key key) {
if (x == null) {
return null;
}
//比较x结点的键和key的大小
int cmp = key.compareTo(x.key);
if (cmp < 0) {
return get(x.left, key);
} else if (cmp > 0) {
return get(x.right, key);
} else {
return x.value;
}
}
}
29.并查集API设计
public class UF {
//记录结点元素和该元素所在分组的标识
private int[] eleAndGroup;
//记录并查集中数据的分组个数
private int count;
//初始化并查集
public UF(int N) {
//初始化分组的数量,默认情况下有N个分组
this.count = N;
//初始化eleAndGroup分组
this.eleAndGroup = new int[N];
//初始化eleAndGroup中的元素及其所在的组的标识符
//让eleAndGroup数组的索引作为并查集的每个结点的元素,并且让每个索引处的值(该元素所在组的标识符)就是该索引
for (int i = 0; i < eleAndGroup.length; i++) {
eleAndGroup[i] = i;
}
}
//获取当前并查集中的数据有多少个分组
public int count() {
return count;
}
//元素p所在分组的标识符
public int find(int p) {
return eleAndGroup[p];
}
//判断并查集中元素p和元素q是否在同一分组中
public boolean connected(int p, int q) {
return find(p) == find(q);
}
//把p元素所在分组和q元素所在分组合并
public void union(int p, int q) {
//判断元素p和q是否已经在同一分组中,如果已经在同一分组中,则结束方法就可以了
if (connected(p, q)) {
return;
}
//找到p所在分组的标识符
int pGroup = find(p);
//找到q所在分组的标识符
int qGroup = find(q);
//合并组,让p所在组的所有元素的组标识符变成q所在分组的标识符
for (int i = 0; i < eleAndGroup.length; i++) {
if (eleAndGroup[i] == pGroup) {
eleAndGroup[i] = qGroup;
}
}
//分组个数-1
this.count--;
}
public static void main(String[] args) {
//创建并查集对象
UF uf = new UF(5);
System.out.println("默认情况下,并查集中有:" + uf.count() + "个分组");
//从控制台录入两个要合并的元素,调用union方法合并,观察合并后并查集中的分组是否减少
Scanner sc = new Scanner(System.in);
while (true) {
System.out.println("请输入第一个要合并的元素:");
int p = sc.nextInt();
System.out.println("请输入第二个要合并的元素:");
int q = sc.nextInt();
//判断这两个元素是否已经在同一组了
if (uf.connected(p, q)) {
System.out.println(p + "元素和" + "q" + "元素已经在同一个组中了");
continue;
}
uf.union(p, q);
System.out.println("当前并查集中还有:" + uf.count() + "个分组");
}
}
}
30.UF_Tree API设计
public class UF_Tree {
//记录结点元素和该元素所在分组的标识
private int[] eleAndGroup;
//记录并查集中数据的分组个数
private int count;
//初始化并查集
public UF_Tree(int N) {
//初始化分组的数量,默认情况下有N个分组
this.count = N;
//初始化eleAndGroup分组
this.eleAndGroup = new int[N];
//初始化eleAndGroup中的元素及其所在的组的标识符
//让eleAndGroup数组的索引作为并查集的每个结点的元素,并且让每个索引处的值(该元素所在组的标识符)就是该索引
for (int i = 0; i < eleAndGroup.length; i++) {
eleAndGroup[i] = i;
}
}
//获取当前并查集中的数据有多少个分组
public int count() {
return count;
}
//判断并查集中元素p和元素q是否在同一分组中
public boolean connected(int p, int q) {
return find(p) == find(q);
}
//元素p所在分组的标识符
public int find(int p) {
while (true) {
if (p == eleAndGroup[p]) {
return p;
}
p = eleAndGroup[p];
}
}
//把p元素所在分组和q元素所在分组合并
public void union(int p, int q) {
//找到p元素和q元素所在组对应的树的根结点
int pRoot = find(p);
int qRoot = find(q);
//如果p和q已经在同一分组,则不需要合并了
if (pRoot == qRoot) {
return;
}
//让p所在的树的根结点的父结点为q所在树的根结点即可
eleAndGroup[pRoot] = qRoot;
//组的数量-1
this.count--;
}
public static void main(String[] args) {
//创建并查集对象
UF_Tree uf = new UF_Tree(5);
System.out.println("默认情况下,并查集中有:" + uf.count() + "个分组");
//从控制台录入两个要合并的元素,调用union方法合并,观察合并后并查集中的分组是否减少
Scanner sc = new Scanner(System.in);
while (true) {
System.out.println("请输入第一个要合并的元素:");
int p = sc.nextInt();
System.out.println("请输入第二个要合并的元素:");
int q = sc.nextInt();
//判断这两个元素是否已经在同一组了
if (uf.connected(p, q)) {
System.out.println(p + "元素和" + "q" + "元素已经在同一个组中了");
continue;
}
uf.union(p, q);
System.out.println("当前并查集中还有:" + uf.count() + "个分组");
}
}
}
31.UF_Tree_Weighted API设计
public class UF_Tree_Weighted {
//记录结点元素和该元素所在分组的标识
private int[] eleAndGroup;
//记录并查集中数据的分组个数
private int count;
//用来存储每一个根结点对应的树中保存的结点的个数
private int[] sz;
//初始化并查集
public UF_Tree_Weighted(int N) {
//初始化分组的数量,默认情况下有N个分组
this.count = N;
//初始化eleAndGroup分组
this.eleAndGroup = new int[N];
//初始化eleAndGroup中的元素及其所在的组的标识符
//让eleAndGroup数组的索引作为并查集的每个结点的元素,并且让每个索引处的值(该元素所在组的标识符)就是该索引
for (int i = 0; i < eleAndGroup.length; i++) {
eleAndGroup[i] = i;
}
this.sz = new int[N];
//默认情况下,sz中每个索引处的值都是1
for (int i = 0; i < sz.length; i++) {
sz[i] = 1;
}
}
//获取当前并查集中的数据有多少个分组
public int count() {
return count;
}
//判断并查集中元素p和元素q是否在同一分组中
public boolean connected(int p, int q) {
return find(p) == find(q);
}
//元素p所在分组的标识符
public int find(int p) {
while (true) {
if (p == eleAndGroup[p]) {
return p;
}
p = eleAndGroup[p];
}
}
//把p元素所在分组和q元素所在分组合并
public void union(int p, int q) {
//找到p元素和q元素所在组对应的树的根结点
int pRoot = find(p);
int qRoot = find(q);
//如果p和q已经在同一分组,则不需要合并了
if (pRoot == qRoot) {
return;
}
//判断proot对应的树大还是qroot对应的树大,最终需要把较小的树合并到较大的树中
if (sz[pRoot] < sz[qRoot]) {
eleAndGroup[pRoot] = qRoot;
sz[qRoot] += sz[pRoot];
} else {
eleAndGroup[qRoot] = pRoot;
sz[pRoot] += sz[qRoot];
}
//组的数量-1
this.count--;
}
public static void main(String[] args) {
//创建并查集对象
UF_Tree_Weighted uf = new UF_Tree_Weighted(5);
System.out.println("默认情况下,并查集中有:" + uf.count() + "个分组");
//从控制台录入两个要合并的元素,调用union方法合并,观察合并后并查集中的分组是否减少
Scanner sc = new Scanner(System.in);
while (true) {
System.out.println("请输入第一个要合并的元素:");
int p = sc.nextInt();
System.out.println("请输入第二个要合并的元素:");
int q = sc.nextInt();
//判断这两个元素是否已经在同一组了
if (uf.connected(p, q)) {
System.out.println(p + "元素和" + "q" + "元素已经在同一个组中了");
continue;
}
uf.union(p, q);
System.out.println("当前并查集中还有:" + uf.count() + "个分组");
}
}
}
32.案例-畅通工程
public class Traffic_Project_Test {
public static void main(String[] args) throws Exception {
//构建一个缓冲读取流
BufferedReader br = new BufferedReader(
new InputStreamReader(Traffic_Project_Test.class.getClassLoader().getResourceAsStream("traffic_project.txt")));
//读取第一行数据20
int totalNumber = Integer.parseInt(br.readLine());
//构建一个并查集对象
UF_Tree_Weighted uf = new UF_Tree_Weighted(totalNumber);
//读取第二行数据7
int roadNumber = Integer.parseInt(br.readLine());
//循环读取7条道路
for (int i = 1; i <= roadNumber; i++) {
String line = br.readLine();//0 1
String[] str = line.split(" ");
int p = Integer.parseInt(str[0]);
int q = Integer.parseInt(str[1]);
//调用并查集对象的union方法让两个城市相通
uf.union(p, q);
}
//获取当前并查集中分组的数量-1就可以得到还需要修建的道路的数目
int roads = uf.count() - 1;
System.out.println("还需要建:" + roads + "条道路,才能实现畅通工程");
}
}