数据结构
1、逻辑结构
- 集合机构:除了属于同一个集合,没有其他关系
- 线性结构:一对一的关系
- 树形结构:一对多的关系
- 图形结构:多对多的关系
2、物理结构
逻辑结构再计算机中真实的表示方式称为物理结构
顺序存储结构:数据元素放在地址连续的存储单元里面,数据间的逻辑关系和物理关系是一致的,查询快,修改慢
链式存储结构:数据放在任意的存储单元里面,可以是连续的也可以是不连续的,所以需要引入指针来存放数据元素的地址,查询慢,修改快
算法分析
时间复杂度
- 事后分析法
- 事前分析法
1.事前分析法
- 算法采用的策略和文案;
- 编译产生的代码质量;
- 输入规模
- 机器执行指令的速度
2.比较算法随着输入规模的增长量时,可以有以下规则:
- 算法函数中的常数可以忽略
- 算法函数中最高次幂的常数因子可以忽略
- 算法函数中最高次幂越小,算法效率越高
3.大O记法
用大写O()来体现算法时间复杂度的记法。
- 用常数1取代运行时间中的所有加法常数
- 只保留最高阶项
- 如果最高阶项存在,且常数因子不为1,则去除与这个项相乘的常数
常见的大O阶
- 线性阶
- 平方阶
- 立方阶
- 对数阶(忽略底数)
- 常数阶
O(1)<O(logn)<O(n)<O(nlogn)<O(n2)<O(n3)
随着n(输入规模)的增大,最高次项相乘的常数可以忽略
空间复杂度
1.java中常见的内存占用
数据类型 | 内存占用字节数 |
---|---|
byte | 1 |
short | 2 |
int | 4 |
long | 8 |
float | 4 |
double | 8 |
boolean | 1 |
char | 2 |
2.计算机访问内存都是一次一个字节一个字节
3.一个引用变量物理地址需要8个字节
4.创建一个对象需要16个字节来保存头信息
5.如果不够8个字节会自动填充为8个字节
public class A {
public int a = 1;
}
/*
* 当我们new A()创建对象时
* 1.int类型占用4个字节
* 2.A对象的头会占用16个字节
* 一共占用20个字节,但自己必须是8的倍数
* 所有系统会自动填充为24个字节
* */
6.java中数组头信息16个字节,会自带4个字节保存长度,又因为20不是8的倍数,所以填充4个字节为24个字节
简单排序
1.使用Comparable排序
创建User类实现Comparable接口
public class Student implements Comparable<Student> {
private String username;
private int age;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"username='" + username + '\'' +
", age=" + age +
'}';
}
@Override
public int compareTo(Student o) {
return this.getAge()-o.getAge();
}
}
测试
public class TestStudent {
public static void main(String[] args) {
Student s1 = new Student();
s1.setUsername("张三");
s1.setAge(18);
Student s2= new Student();
s2.setUsername("李四");
s2.setAge(22);
Comparable max = getMax(s1, s2);
System.out.println(max);
}
public static Comparable getMax(Comparable c1,Comparable c2){
int result = c1.compareTo(c2);
//result<0,c1小于c2
//result>0,c1大于c2
//result==0,c1等于c2
if(result>=0){
return c1;
}else {
return c2;
}
}
}
2.冒泡排序
冒泡排序代码
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){
//v.compareTo(w)返回v-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;
}
}
测试
public class BubbleTest {
public static void main(String[] args) {
Integer[] arr = {4, 5, 6, 3, 2, 1};
Bubble.sort(arr);
System.out.println(Arrays.toString(arr));
}
}
时间复杂度分析
以最坏情况为标准{6,5,4,3,2,1}
- 元素比较的次数: (N-1)+(N-2)+…+2+1 = N^2/2-N/2
- 交换次数(最坏情况每次都交换):N^2/2-N/2
- 总次数:N^2-N
- 大O记法:O(N^2)
3.选择排序
选择排序代码
public class Selection {
/*
* 对a进行排序
* */
public static void sort(Comparable[] a){
for (int i = 0; i <a.length ; i++) {
//记录最小元素的索引
int min = i;
for (int j=i+1;j<a.length;j++){
//如果最小索引处的值大于j处索引的值
if (greater(a[min],a[j])){
//最小索引更改为j处索引
min = j;
}
}
//交换最小索引和i处索引的值
exch(a,min,i);
}
}
//比较元素v是否大于w
private static boolean greater(Comparable v,Comparable w){
//v.compareTo(w)返回v-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;
}
}
测试
public class SelectionTest {
public static void main(String[] args) {
Integer[] arr = {4,6,8,3,7,9,2,10,1};
Selection.sort(arr);
System.out.println(Arrays.toString(arr));
}
}
时间复杂度分析
以最坏情况为标准{10,9,8,7,6,5,4,3,2,1}
- 元素比较的次数: (N-1)+(N-2)+…+2+1 = N^2/2-N/2
- 交换次数(每次循环只交换一次):N-1
- 总次数:N^2/2+N/2-1
- 大O记法:O(N^2)
4.插入排序
插入排序的代码
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处的值大于j处的值,交换位置
if (greater(a[j-1],a[j])){
exch(a,j,j-1);
}else {
break;
};
}
}
}
//比较元素v是否大于w
private static boolean greater(Comparable v,Comparable w){
//v.compareTo(w)返回v-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;
}
}
测试
public class InsertionTest {
public static void main(String[] args) {
Integer[] a = {4,3,2,10,12,1,5,6};
Insertion.sort(a);
System.out.println(Arrays.toString(a));
}
}
时间复杂度分析
以最坏情况为标准{12,10,6,5,4,3,2,1}
- 元素比较的次数:1+2+…+ (N-2)+(N-1) = N^2/2-N/2
- 交换次数: (N-1)+(N-2)+…+2+1 = N^2/2-N/2
- 总次数:N^2-N
- 大O记法:O(N^2)
高级排序
1.希尔排序
希尔排序代码
public class Shell {
/*
* 对a进行排序
* */
public static void sort(Comparable[] a){
//1.根据a的长度,确定增长量h的值
int h =1;
while (h<a.length/2){
h=2*h+1;
}
while (h >= 1) {
//排序
//找到待插入元素(就是h对应的索引)
for (int i=h;i<a.length;i++){
//把待插入元素插入到有序数组中
for (int j = i; j >=h ; j-=h) {
//待插入的元素时a[j],比较前一个元素a[j-h]
if (greater(a[j-h],a[j])){
exch(a,j-h,j);
}else {
break;
}
}
}
//减少h的值
h = h/2;
}
}
//比较元素v是否大于w
private static boolean greater(Comparable v,Comparable w){
//v.compareTo(w)返回v-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;
}
}
测试
public class ShellTest {
public static void main(String[] args) {
Integer[] a = {9,1,2,5,7,4,8,6,3,5};
Shell.sort(a);
System.out.println(Arrays.toString(a));
}
}
时间复杂度分析
希尔排序不适用事前分析法,使用事后分析法比较,运行10000个逆序数据排序的效率比插入排序快1000多倍(只是大概数据,快就完事了)
2.归并排序
归并排序代码
public class Merge {
//归并需要使用的辅助数组
private static Comparable[] assist;
//比较元素v是否大于w
private static boolean less(Comparable v,Comparable w){
//v.compareTo(w)返回v-w的结果
return v.compareTo(w)<0;
}
/*
* 对a进行排序
* */
public static void sort(Comparable[] a){
//1.初始化辅助数组
assist = new Comparable[a.length];
//2.定义lo和hi,分别是最小索引和最大索引
int lo = 0;
int hi = a.length-1;
//3.调用sort重载方法对lo到hi的元素排序
sort(a,lo,hi);
}
//对lo到hi的元素进行排序
public 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到hi的一组数据进行归并
public 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];
}
}
}
测试
public class MergeTest {
public static void main(String[] args) {
Integer[] a = {8,4,5,7,1,3,6,2};
Merge.sort(a);
System.out.println(Arrays.toString(a));
}
}
时间复杂度分析
假设有n个元素,拆分的次数是log2(n),最终时间复杂度为log2(n)*2^(log2(n))=nlog2(n)
大O记法O(nlogn)
缺点
需要使用辅助数组额外申请空间,典型的以空间换时间
3.快速排序
快速排序代码
public class Quick {
//比较元素v是否大于w
private static boolean less(Comparable v,Comparable w){
//v.compareTo(w)返回v-w的结果
return v.compareTo(w)<0;
}
//数组元素i和j交换位置
private static void exch(Comparable[] a,int i,int j){
Comparable temp = a[i];
a[i]=a[j];
a[j]=temp;
}
//对数据排序
public static void sort(Comparable[] a){
int lo=0;
int hi = a.length-1;
sort(a,lo,hi);
}
//对从lo到hi的数据排序
public static void sort(Comparable[] a,int lo,int hi){
//安全性检查
if(lo>=hi){
return;
}
//对数组进行分组
int partition = partition(a, lo, hi);//位置变换后的索引
//让左子组有序
sort(a, lo, partition-1);
//让右子组有序
sort(a, partition+1, hi);
}
public static int partition(Comparable[] a,int lo,int hi) {
//确定分界值
Comparable key = a[lo];
//定义两个指针,分别指向待切分元素的最小索引处和最大索引处的下一个位置
int left=lo;
int right = hi+1;
while (true){
//从右往左扫描找到比分界值小的值停止
while (less(key,a[--right])){
if (right==lo){
break;
}
}
//从左往右扫描找到比分界值大的值停止
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;
}
}
测试
public class QuickTest {
public static void main(String[] args) {
Integer[] a = {6,1,2,7,9,3,4,5,8};
Quick.sort(a);
System.out.println(Arrays.toString(a));
}
}
时间复杂度分析
- 最优情况O(nlogn)
- 最坏情况O(n^2)
- 平均情况O(nlogn)
排序的稳定性
稳定性定义:对数组中两个相等的元素A和B,如果排序前与排序后AB的前后位置不变,算法就是稳定的,否则为不稳定的
稳定的排序算法:
- 冒泡排序
- 插入排序
- 归并排序
不稳定的排序算法:
- 选择排序
- 希尔排序
- 快速排序
线性表
前驱元素:若A元素再B元素的前面,则称A是B的前驱元素
后继元素:若A元素再B元素的后面,则称B是A的后继元素
线性表的特征:数据元素之间具有一种“一对一”的逻辑关系
- 第一个元素没有前驱元素,这个元素称为头节点
- 最后一个元素没有后继元素,这个数据元素被称为尾节点
- 除了第一个和最后一个,其他元素有且仅有一个前驱和一个后继
顺序表
手动创建一个顺序表
public class SequenceList<T> {
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];
}
public void insert(T t){
eles[N++]=t;
}
public void insert(int index,T value) {
for (int i = this.N - 1; i >= index; i--) {
eles[i + 1] = eles[i]; //依次后移
}
eles[index] = value;
this.N++; //数组元素下标增加
}public T remove(int index) {
T t;
t = eles[index];
for (int j = index; j <= this.N - 2; j++) { //这里-2,是因为找到的元素下标,要将后一个的冲掉前一个,会增加一个
eles[j] = eles[j + 1];
}
this.N--;
return t;
}
}
顺序表的遍历
public class SequenceList<T> implements Iterable<T>
@Override
public Iterator<T> 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++];
}
}
顺序表扩容
public void resize(int newsize){
T[] temp = eles;
eles = (T[]) new Object[newsize];
for (int i=0;i<N;i++){
eles[i]=temp[i];
}
}
删除时
if (N<eles.length/4){
resize(eles.length/2);
}
添加时
if (N==eles.length){
resize(2*eles.length);
}
时间复杂度分析
get方法:O(1)
insert方法:O(n)
remove方法:O(n)
Java中的ArrayList
- 底层是一个数组
- 有扩容操作
- 提供了遍历的方式
和我们写的顺序表如出一辙
链表
单向链表
它的头节点不存储数据,是用来找到链表的,是链表的入口
手动创建单向链表
public class LinkList<T> implements Iterable<T>{
//头节点
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 LinkList(){
//初始化头节点
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;
}
//获得指定位置的元素
public T get(int i){
//通过循环从头节点往后找
Node n = head.next;
for (int index = 0; index <i; index++) {
n=n.next;
}
return n.item;
}
//向链表添加元素
public void insert(T t){
//找到最后一个节点
Node n = head;
while (n.next!=null){
n = n.next;
}
//创建新节点
Node newnode = new Node(t, null);
//让最后的节点指向新节点
n.next = newnode;
//元素个数+1
N++;
}
//向链表指定位置添加元素
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-1节点指向新节点
pre.next = newnode;
//元素个数加一
N++;
}
//删除指定元素
public T remove(int i){
//找到i-1
Node pre = head;
for (int index = 0; index <=i-1 ; index++) {
pre= pre.next;
}
//找到i
Node removeNode = pre.next;
//找到i+1
Node curr = removeNode.next;
//i-1指向i+1
pre.next = curr;
//个数减一
N--;
return removeNode.item;
}
public int indexOf(T t){
Node n = head;
for (int i =0;n.next!=null;i++){
n=n.next;
if (n.item.equals(t)){
return i;
}
}
return -1;
}
@Override
public Iterator<T> iterator() {
return new LInterator();
}
private class LInterator implements Iterator{
private Node n;
public LInterator(){
this.n=head;
}
@Override
public boolean hasNext() {
return n.next!=null;
}
@Override
public Object next() {
n = n.next;
return n.item;
}
}
}
双向链表
它的头节点不存储数据,是用来找到链表的,是链表的入口
手写双向链表代码
public class ToWayLinkList<T> implements Iterable<T> {
//头节点
private Node head;
//尾节点
private Node last;
//记录链表长度
private int N;
//结点类
private class Node {
private T item;
private Node pre;
private Node next;
public Node(T item, Node pre, Node next) {
this.item = item;
this.pre = pre;
this.next = next;
}
}
public ToWayLinkList() {
this.head = new Node(null, null, null);
this.last = null;
this.N = 0;
}
//清空链表
public void clear() {
this.head.next = null;
this.last = null;
this.N = 0;
}
//获取链表的长度
public int length() {
return N;
}
//判断链表是否为空
public boolean isEmpty() {
return N == 0;
}
//向链表添加元素
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++;
}
//向链表指定位置添加元素
public void insert(int i, T t) {
//得到第i-1节点
Node preNode = head;
for (int index = 0; index <= i - 1; index++) {
preNode = preNode.next;
}
//得到第i节点
Node curr = preNode.next;
//创建新节点
Node newnode = new Node(t, preNode, curr);
//i-1指向新节点
preNode.next = newnode;
//i节点指向新节点
curr.pre = newnode;
//长度加一
N++;
}
//获得指定位置的元素
public T get(int i) {
Node n = head;
for (int index = 0; index <=i ; index++) {
n = n.next;
}
return n.item;
}
//得到指定节点的索引
public int indexOf(T t) {
Node n = head;;
for (int i = 0;n.next!=null;i++){
n=n.next;
if (n.item.equals(t)){
return i;
}
}
return -1;
}
//删除指定元素
public T remove(int i) {
//找到i-1
Node pre = head;
for (int index = 0; index <=i-1; index++) {
pre=pre.next;
}
//得到i
Node curr = pre.next;
//找到i+1
Node next = curr.next;
//i-1的next指向i+1
pre.next = next;
//i+1的pre指向i-1
next.pre = pre;
//长度减一
N--;
return curr.item;
}
//查找第一个节点
public T getFirst() {
if (isEmpty()) {
return null;
}
return head.next.item;
}
//查找最后一个节点
public T getLast() {
if (isEmpty()) {
return null;
}
return last.item;
}
@Override
public Iterator<T> iterator() {
return new TIterator();
}
private class TIterator implements Iterator{
private Node n;
public TIterator(){
this.n = head;
}
@Override
public boolean hasNext() {
return n.next!=null;
}
@Override
public T next() {
n=n.next;
return n.item;
}
}
}
Java中的LinkedList
- 底层使用双向链表实现的
- 源码中使用first代表第一个存储数据的节点
- 我们使用head代表头节点
时间复杂度分析
get方法:O(n)
insert方法:O(n)
remove方法:O(n)
虽然添加删除时间复杂度和顺序表相同,但是他的时间是浪费在for循环查找上,并没有进行数据移动
又因为他的物理地址是任意的,她不用预先指定存储空间的大小
链表反转
//反转整个链表
public void reverse(){
//当前链表是否为空
if(isEmpty()){
return;
}
reverse(head.next);
}
//反转指定节点
public Node reverse(Node curr){
if (curr.next==null){
//如果是最后一个节点,就让头节点指向它就可以了
head.next=curr;
//并把最后一个节点返回
return curr;
}
//递归反转下一个节点
Node pre = reverse(curr.next);
pre.next = curr;
//为了让原本的第一个的next变为null,不然他会指向2
curr.next=null;
return curr;
}
快慢指针
快指针一般是慢指针的两倍
解决中间值问题
//快慢指针求中间值
public T getMid(){
Node fast = head.next;
Node slow = fast;
while (fast.next!=null&&fast!=null){
fast = fast.next.next;
slow = slow.next;
}
return slow.item;
}
判断单项链表是否有环
//快慢指针判断是否有环
public boolean isCircle(){
Node fast = head.next;
Node slow = fast;
while (fast.next!=null&&fast!=null){
fast = fast.next.next;
slow = slow.next;
if (fast.equals(slow)){
return true;
}
}
return false;
}
得到环入口
//得到环的入口
public Node getEntrance() {
Node fast = head.next;
Node slow = fast;
Node temp = null;
while (fast.next != null && fast != null) {
fast = fast.next.next;
slow = slow.next;
if (fast.equals(slow)) {
temp = head.next;
continue;
}
if (temp != null) {
temp = temp.next;
if (temp.equals(slow)) {
break;
}
}
}
return temp;
}
循环链表
最后一个节点的next指向第一个节点即可
约瑟夫问题
public class JosephTest {
public static void main(String[] args) {
//首节点
Node<Integer> first =null;
//前一个节点
Node<Integer> pre =null;
//构建循环链表
for (int i = 1; i <=41 ; i++) {
//如果是第一个
if(i==1){
first = new Node<>(i,null);
pre = first;
continue;
}
//如果不是第一个
Node<Integer> newNode = new Node<>(i, null);
pre.next = newNode;
pre = newNode;
//如果是最后一个
if (i==41){
pre.next=first;
}
}
//计数器
int count = 0;
//记录每次遍历拿到的节点
Node<Integer> n = first;
//当前节点的上一个节点
Node<Integer> before = null;
while (n!=n.next){
//模拟报数
count++;
//判断是不是3
if (count==3){
//删除3的节点
before.next = n.next;
System.out.print(n.item+",");
count = 0;
n=n.next;
}else {
//不是3节点后移
before = n;
n = n.next;
}
}
//打印最后一个元素
System.out.println("||"+n.item);
}
//节点类
private static class Node<T> {
T item;
Node next;
public Node(T item, Node next) {
this.item = item;
this.next = next;
}
}
}
栈
栈是一种先进后出(FILO)的数据结构,是一种只能在一端进行插入和删除操作的特殊线性表。
数据进图栈的动作称为压栈,数据从栈出去的动作为弹栈
用链表实现栈
public class Stack<T> implements Iterable<T> {
//首结点
private Node head;
//栈中元素个数
private int N;
private class Node {
private T item;
private 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;
}
//判断当前是否为空
public boolean isEmpty() {
return N == 0;
}
//获取栈中元素的个数
public int size() {
return N;
}
//把t元素压入栈
public void push(T t) {
Node oldFirsh = head.next;
Node newNode = new Node(t, null);
head.next = newNode;
newNode.next = oldFirsh;
N++;
}
//弹出栈顶元素
public T pop() {
Node oldfirsh = head.next;
if (oldfirsh == null) {
return null;
}
Node firsh = oldfirsh.next;
head.next = firsh;
N--;
return oldfirsh.item;
}
@Override
public Iterator<T> iterator() {
return new SIterator();
}
private class SIterator implements Iterator {
private Node n;
public SIterator() {
this.n = head ;
}
@Override
public boolean hasNext() {
return n.next!=null;
}
@Override
public Object next() {
n=n.next;
return n.item;
}
}
}
判断括号是否成对出现
public class BracketsMatchTest {
public static void main(String[] args) {
String str = "(上海(长安)())";
boolean match = isMatch(str);
System.out.println(match);
}
public static boolean isMatch(String str){
Stack<String> objects = new Stack<>();
for (int i = 0; i < str.length(); i++) {
String s = str.charAt(i)+"";
if ("(".equals(s)){
objects.push("1");
}
if (")".equals(s)){
objects.pop();
}
}
return objects.isEmpty();
}
}
逆波兰表达式求值
public class ReversePolishNotationTest {
public static void main(String[] args) {
//中缀表达式 3*(17-15)+18/6 的逆波兰表达式如下
String[] notation = {"3", "17", "15", "-", "*", "18", "6", "/", "+"};
int result = caculate(notation);
System.out.println("结果为" + result);
}
public static int caculate(String[] strings) {
Stack<Integer> s = new Stack<>();
Integer p1 = null;
Integer p2 = null;
for (int i = 0; i < strings.length; i++) {
String curr = strings[i];
switch (curr) {
case "+":
p1 = s.pop();
p2 = s.pop();
s.push(p2+p1);
break;
case "-":
p1 = s.pop();
p2 = s.pop();
s.push(p2-p1);
break;
case "*":
p1 = s.pop();
p2 = s.pop();
s.push(p2*p1);
break;
case "/":
p1 = s.pop();
p2 = s.pop();
s.push(p2/p1);
break;
default:
s.push(Integer.valueOf(curr));
break;
}
}
return s.pop();
}
}
队列
队列是一种基于先进先出(FIFO)的数据结构,在一段进行插入,在另一端进行删除的特殊线性表,它按照先进先出的原则储存数据。
public class Queue<T> implements Iterable<T> {
//首结点
private Node head;
//栈中元素个数
private int N;
//尾结点
private Node last;
private class Node {
private T item;
private 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 boolean isEmpty() {
return N == 0;
}
//获取栈中元素的个数
public int size() {
return N;
}
//把t元素压入栈
public void enqueue(T t) {
Node newNode = new Node(t,null);
if (last==null){
head.next=newNode;
last = newNode;
}else {
last.next = newNode;
last=newNode;
}
N++;
}
//弹出栈顶元素
public T dequeue() {
if (isEmpty()){
return null;
}
Node oldFirst = head.next;
head.next = oldFirst.next;
N--;
if (isEmpty()){
last=null;
}
return oldFirst.item;
}
@Override
public Iterator<T> iterator() {
return new QIterator();
}
private class QIterator implements Iterator<T>{
private Node n;
public QIterator(){
this.n=head;
}
@Override
public boolean hasNext() {
return n.next!=null;
}
@Override
public T next() {
n=n.next;
return n.item;
}
}
}
符号表
符号表最主要的目的就是将一个键和一个值联系起来,可以根据键查找对应的值。
符号表中,键具有唯一性
无序符号表
public class SymbolTable<key, value> {
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 node) {
this.key = key;
this.value = value;
this.next = node;
}
}
public SymbolTable() {
this.head = new Node(null, null, null);
this.N = 0;
}
public int size() {
return N;
}
public void put(key key, value value) {
Node n = head;
while (n.next != null) {
n = n.next;
if (n.key.equals(key)) {
//如果符号表中有key,找到该节点,替换为value
n.value = value;
return;
}
}
Node newnode = new Node(key, value, null);
Node oldfirst = head.next;
head.next = newnode;
newnode.next = oldfirst;
N++;
}
public value get(key key) {
Node n = head;
while (n.next != null) {
n=n.next;
if (n.key.equals(key)) {
return n.value;
}
}
return null;
}
public value delete(key key) {
Node n = head;
Node del = null;
while (n.next != null) {
if (n.next.key.equals(key)) {
del = n.next;
n.next = del.next;
N--;
return del.value;
}
n=n.next;
}
return null;
}
}
有序符号表
改变put方法就可以
public void put(key key, value value) {
Node curr = head.next;
Node pre = head;
while (curr != null && key.compareTo(curr.key) > 0) {
pre = curr;
curr = curr.next;
}
if (curr != null && key.compareTo(curr.key) == 0) {
curr.value = value;
return;
}
Node newnode = new Node(key, value, null);
pre.next = newnode;
newnode.next = curr;
N++;
}
二叉树
树具有以下特点:
- 每个结点有零个或多个子结点
- 没有父结点的结点为根节点
- 每一个非根结点只有一个父结点
- 每个结点及其后代结点整体上可以看做一棵树,称为当前结点的父结点的一个子树
树的相关术语
-
结点的度
一个结点含有子树的个数
-
叶节点
度为零的结点称为叶节点,也称为终端结点
-
分支节点
度不为零的结点称为分支节点,也称为非终端结点
-
结点的层次
根节点的层次是1,依次类推
-
结点的层序编号
从上往下从左往右,依次编号
-
树的度
最大的度称为树的度
-
树的高度
树中结点的最大层次
-
森林
去掉根节点,形成互补相交的树
-
孩子结点
一个结点的直接后继结点称为该结点孩子结点
-
双亲结点(父结点)
一个结点直接前驱称为该结点的双亲结点
-
兄弟结点
同一双亲结点的孩子节点
二叉树的基本定义
二叉树就是度不超过2的树
满二叉树:每一层的结点都达到最大值
完全二叉树:叶节点只能出现在最下层和次下层,最下层的结点都出现在该层的最左边
二叉查找树
public class BinaryTree<key extends Comparable<key>, 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;
}
public void put(key key, value value) {
root = put(root, key, value);
}
public Node put(Node x, key key, value value) {
if (x == null) {
N++;
return new Node(key, value, null, null);
}
int cmp = key.compareTo(x.key);
if (cmp > 0) {
x.right = put(x.right, key, value);
} else if (cmp < 0) {
x.left = put(x.left, key, value);
} else {
x.value = value;
}
return x;
}
public value get(key key) {
return get(root, key);
}
public value get(Node x, key key) {
if (x == null) {
return null;
}
int tmp = key.compareTo(x.key);
if (tmp > 0) {
return get(x.right, key);
} else if (tmp < 0) {
return get(x.left, key);
} else {
return x.value;
}
}
public void delete(key key) {
delete(root, key);
}
public Node delete(Node x, key key) {
if (x == null) {
return null;
}
int tmp = key.compareTo(x.key);
if (tmp > 0) {
x.right = delete(x.right, key);
} else if (tmp < 0) {
x.left = delete(x.left, key);
} else {
if (x.right == null) {
N--;
return x.left;
}
if (x.left == null) {
N--;
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.left;
}
}
minNode.right = x.right;
minNode.left = x.left;
x = minNode;
N--;
}
return x;
}
public key min() {
return min(root).key;
}
public Node min(Node x) {
if (x.left != null) {
return min(x.left);
} else {
return x;
}
}
public key max() {
return max(root).key;
}
public Node max(Node x) {
if (x.right != null) {
return max(x.right);
} else {
return x;
}
}
}
二叉树的基础遍历
-
前序遍历
先访问根节点,然后在访问左子树,最后访问右子树
-
中序遍历
先访问左子树,中间访问根节点,最后访问右子树
-
后序遍历
先访问左子树,在访问右子树,最后访问根节点
1、前序遍历
//获取这个树的所有的key
public Queue<key> preErgodic(){
Queue<key> keys = new Queue<>();
preErgodic(root,keys);
return keys;
}
//在指定树中获取所有的键,并放到key中
private void preErgodic(Node x,Queue<key> keys){
if (x==null){
return;
}
//把x的key放入Queue中
keys.enqueue(x.key);
//递归遍历左子树
if (x.left!=null){
preErgodic(x.left,keys);
}
//递归遍历右子树
if (x.right!=null){
preErgodic(x.right,keys);
}
}
2、中序遍历
//获取这个树的所有的key
public Queue<key> midErgodic(){
Queue<key> keys = new Queue<>();
midErgodic(root,keys);
return keys;
}
//在指定树中获取所有的键,并放到key中
private void midErgodic(Node x,Queue<key> keys){
if (x==null){
return;
}
//递归遍历左子树
if (x.left!=null){
midErgodic(x.left,keys);
}
//把x的key放入Queue中
keys.enqueue(x.key);
//递归遍历右子树
if (x.right!=null){
midErgodic(x.right,keys);
}
}
3、后序遍历
//获取这个树的所有的key
public Queue<key> afterErgodic(){
Queue<key> keys = new Queue<>();
afterErgodic(root,keys);
return keys;
}
//在指定树中获取所有的键,并放到key中
private void afterErgodic(Node x,Queue<key> keys){
if (x==null){
return;
}
//递归遍历左子树
if (x.left!=null){
afterErgodic(x.left,keys);
}
//递归遍历右子树
if (x.right!=null){
afterErgodic(x.right,keys);
}
//把x的key放入Queue中
keys.enqueue(x.key);
}
层序遍历
public Queue<key> layerErgodic(){
Queue<key> keys = new Queue<>();
Queue<Node> 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 Queue<key> layerErgodic(){
Queue<key> keys = new Queue<>();
Queue<Node> 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);
}
public int maxDepth(Node x){
if (x==null){
return 0;
}
int max = 0;
int maxl = 0;
int maxr = 0;
//计算左子树最大深度
if (x.left!=null){
maxl = maxDepth(x.left);
}
//计算右子树最大深度
if (x.right!=null){
maxr = maxDepth(x.right);
}
//比较右子树最大什么,取最大值+1
max = maxl>maxr?maxl+1:maxr+1;
return max;
}
折纸问题
public class PageFoldingTest {
public static void main(String[] args) {
//生成树
Node tree = createTree(2);
//打印
printTree(tree);
}
//模拟对着N次纸
public static Node createTree(int N){
//定义根节点
Node<String> root = null;
for (int i = 0; i <N ; i++) {
//1.当前是第一次对折
if (i == 0) {
root = new Node<>("down",null,null);
continue;
}
//2.不是第一次
Queue<Node> queue = new Queue<>();
queue.enqueue(root);
//遍历队列
while (!queue.isEmpty()){
//弹出一个结点
Node<String> tmp = queue.dequeue();
if (tmp.left!=null){
queue.enqueue(tmp.left);
}
if (tmp.right!=null){
queue.enqueue(tmp.right);
}
if (tmp.right==null&&tmp.left==null){
tmp.right = new Node<String>("up",null,null);
tmp.left = new Node<String>("down",null,null);
}
}
}
return root;
}
//打印树中的每个结点
public static void printTree(Node<String> root){
//使用中序遍历
if (root==null){
return;
}
//打印左子树的每个结点
if (root.left!=null){
printTree(root.left);
}
//打印当前结点
System.out.print(root.item+" ");
//打印右子树的每个结点
if (root.right!=null){
printTree(root.right);
}
}
public static class Node<T>{
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;
}
}
}
堆
堆的特性:
- 他是完全二叉树
- 它通常用数组来实现
- 如果一个结点的位置时k,那么它的父结点位置为k/2
- k结点的子结点分别为2k,2k+1
- 从a[k]向上一层,就令k等于k/2,向下一层就令k等于2k或2k+1
- 每个节点都大于等于它的两个子结点。两个子结点没有大小要求
堆的代码
public class Heap<T extends Comparable<T>> {
//存储元素的数组
private T[] items;
//记录堆中元素的个数
private int N;
//创建容量为capacity的Heap对象
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);
}
//删除最大的元素,并返回这个元素
public T delMax(){
//记录下最大元素
T max = items[1];
//交换最大元素和最后一个索引
exch(1,N);
//删除最后一个元素
items[N] = null;
//长度减一
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;
}
exch(k,max);
k = max;
}
}
}
堆排序
public class HeapSort{
private static boolean less(Comparable[] heap,int i,int j){
return heap[i].compareTo(heap[j])<0;
}
private static void exch(Comparable[] heap,int i,int j){
Comparable tmp = heap[i];
heap[i] = heap[j];
heap[j] = tmp;
}
private static void createHeap(Comparable[] source,Comparable[] heap){
//把source中的元素拷贝到heap中
System.arraycopy(source,0,heap,1,source.length);
//对堆中的元素做下沉调整(从长度的一半处开始)
for (int i=(heap.length/2);i>0;i--){
sink(heap,i,heap.length-1);
}
}
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的数据复制到原数组
System.arraycopy(heap,1,source,0,source.length);
}
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;
}
}
}
优先队列
最大优先队列
可以获取并删除队列中最大的值
public class MaxPriorityQueue<T extends Comparable<T>> {
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;
}
private boolean less(int i,int j){
return items[i].compareTo(items[j])<0;
}
private void exch(int i,int j){
T tmp = items[i];
items[i] = items[j];
items[j] = tmp;
}
public void insert(T t){
items[++N] = t;
swim(N);
}
public T delMax(){
T max = items[1];
exch(1,N);
N--;
sink(1);
return max;
}
private void swim(int k){
while (k>1){
if (less(k/2,k)){
exch(k/2,k);
}
k = k/2;
}
}
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;
}
exch(k,max);
k = max;
}
}
}
最小优先队列
可以获取并删除队列中最小的值
public class MinPriorityQueue<T extends Comparable<T>> {
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;
}
private boolean less(int i, int j) {
return items[i].compareTo(items[j]) < 0;
}
private void exch(int i, int j) {
T tmp = items[i];
items[i] = items[j];
items[j] = tmp;
}
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;
}
private void swim(int k) {
while (k > 1) {
if (less(k,k/2)) {
exch(k,k/2);
}
k = k / 2;
}
}
private void sink(int k) {
while (2 * k <= N) {
int min;
if (2 * k + 1 <= N) {
if (less(2 * k+1, 2 * k)) {
min = 2 * k + 1;
} else {
min = 2 * k;
}
} else {
min = 2 * k;
}
if (less(k,min)) {
break;
}
exch(k, min);
k = min;
}
}
}
最小索引优先队列
public class IndexMinpriorityQueue<T extends Comparable<T>> {
private T[] items;
private int[] 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;
//默认情况下,没有存储的元素都为-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处的值
public boolean less(int i, int j) {
return items[pq[i]].compareTo(items[pq[j]]) < 0;
}
//交换i处和j处的值
public void exch(int i, int j) {
int tmp = pq[i];
pq[i] = pq[j];
pq[j] = tmp;
qp[pq[i]] = i;
qp[pq[j]] = j;
}
//判断k对应的元素是否存在
public boolean contains(int k) {
return qp[k] != -1;
}
//最小元素关联的索引
public int minIndex() {
return pq[1];
}
//向队列插入一个元素,并关联索引
public void insert(int i, T t) {
//判断i处是否有值
if (contains(i)) {
return;
}
//个数加一
N++;
//把数据存在items
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];
//交换k处的值和和N处的值
exch(i, N);
//删除qp中的内容
qp[pq[N]] = -1;
//删除pq中的内容
pq[N] = -1;
//删除items中的内容
items[pq[k]] = null;
//元素数量减一
N--;
//堆调整
swim(k);
sink(k);
}
//把i处的元素修改为t
public void changeItem(int i, T t) {
//修改items中的元素
items[i] = t;
//找到i在pq中出现的位置
int k = qp[i];
//堆调整
swim(k);
sink(k);
}
//上浮算法
private void swim(int k) {
while (k>1){
if (less(k,k/2)){
exch(k,k/2);
}
k = k/2;
}
}
//下沉算法
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;
}
k = min;
}
}
}
平衡树
红黑树代码
类似于2-3查找树
public class RedBlackTree<Key extends Comparable<Key>, 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;
private 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;
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;
h.color = RED;
return x;
}
//颜色反转
private void flipColors(Node h) {
h.right.color = BLACK;
h.left.color = BLACK;
h.color = RED;
}
//在整个树上插入
public void put(Key key, Value value) {
root = put(root, key, value);
root.color = BLACK;
}
//在指定树中完成插入
private Node put(Node h, Key key, Value value) {
//h是否为空
if (h == null) {
N++;
return new Node(key, value, null, null, RED);
}
//比较h结点的键和key的大小
int cmp = key.compareTo(h.key);
if (cmp < 0) {
//继续往左
h.left = put(h.left, key, value);
} else if (cmp > 0) {
//继续往右
h.right = put(h.right, key, value);
} else {
//直接替换
h.value = value;
}
//左旋:当前结点左子结点为红色 右子结点时红色
if (isRed(h.right) && !isRed(h.left)) {
h = rotateLeft(h);
}
//右旋
if (isRed(h.left) && isRed(h.left.left)) {
h = rotateRight(h);
}
//颜色反转
if (isRed(h.right) && isRed(h.left)) {
flipColors(h);
}
return h;
}
//根据key得到对应的值
public Value get(Key key) {
return get(root, key);
}
//在指定的树x中找到对应的值
private Value get(Node x, Key key) {
if (x == null) {
return null;
}
//比较x的key和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;
}
}
}
B树
构造B树需要有一个参数M,可以构造出M阶B树,具有以下特点:
- 每个结点最多有M-1个key,并且以升序排列
- 每个节点最多能有M个子结点
- 根节点至少有两个子结点
B+树
- 只有叶子结点存放数据
- 其他结点只是索引的功能
- 在数据库中索引就是B+树实现的
并查集
并查集是一种树型的数据结构,并查集可以高效地进行以下操作:
- 查询元素p和元素q是否属于同一组
- 合并元素p和元素q所在的组
并查集结构
并查集也是一种树型结构,这种树的要求简单:
- 每个元素都唯一的对应一个结点;
- 每一组数据中的多个元素都在同一棵树中;
- 一个组中的数据对应的树和另外一个组中的数据对应的树之间没有任何联系;
- 元素在书中并没有子父关系的硬性要求;
public class UF {
//记录结点元素和该元素所在分组的标记
private int[] eleAndGroup;
//记录并查集中数据的分组个数
private int count;
public UF(int N){
//初始化分组的数量
this.count = N;
//初始eleAndGroup数组
this.eleAndGroup = new int[N];
//初始化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){
if (connected(p,q)){
return;
}
int pGroup = find(p);
int qGroup = find(q);
for (int i = 0; i < eleAndGroup.length; i++) {
if (eleAndGroup[i]==pGroup){
eleAndGroup[i] = qGroup;
}
}
this.count--;
}
}
并查集优化
public class UF_Tree {
private int[] eleAndGroup;
private int count;
public UF_Tree(int N){
this.count = N;
this.eleAndGroup = new int[N];
for (int i = 0; i <eleAndGroup.length; i++) {
eleAndGroup[i] = i;
}
}
public int count() {
return count;
}
public boolean connected(int p,int q){
return find(p)==find(q);
}
public int find(int p){
while (true){
if (p==eleAndGroup[p]){
return p;
}
p = eleAndGroup[p];
}
}
public void union(int p,int q){
//找到p和q所在组对应的树的根
int pGroup = find(p);
int qGroup = find(q);
if (pGroup==qGroup){
return;
}
eleAndGroup[pGroup] = qGroup;
this.count--;
}
}
图
无向图
public class Graph {
//经典数目
private final int v;
//边的数目
private int E;
//邻接表
private Queue<Integer>[] adj;
public Graph(int V) {
this.v = V;
this.E = 0;
this.adj = new Queue[V];
for (int i = 0; i < adj.length; i++) {
adj[i] = new Queue<Integer>();
}
}
//获取顶点数目
public int V() {
return v;
}
//获取边的数目
public int E() {
return E;
}
//添加一条边
public void addEdge(int v, int w) {
adj[v].enqueue(w);
adj[w].enqueue(v);
E++;
}
//获取和v相邻的所有顶点
public Queue<Integer> adj(int v) {
return adj[v];
}
}
深度优先搜索
public class DepthFirstSearch {
//索引代表顶点,值表示当前顶点是否已经被搜索
private boolean[] marked;
//记录有多少个顶点与s顶点相同
private int count;
public DepthFirstSearch(Graph graph,int s){
//初始化marked
this.marked = new boolean[graph.V()];
this.count = 0;
dfs(graph,s);
}
//查询v顶点的所有相同顶点
private void dfs(Graph g,int v){
//v记录以搜索
marked[v] = true;
for (Integer w : g.adj(v)) {
if (!marked[w]){
dfs(g,w);
}
}
count++;
}
//判断w顶点与s顶点是否相通
public boolean marked(int w){
return marked[w];
}
//获取与顶点s相同的所有顶点总数
public int count(){
return count;
}
}
广度优先搜索
public class BreadthFirstSearch {
//索引代表顶点,值表示当前顶点是否已经被搜索
private boolean[] marked;
//记录有多少个顶点与s顶点相同
private int count;
//用来存储待搜索的点
private Queue<Integer> waitSearch;
public BreadthFirstSearch(Graph graph,int s){
//初始化marked
this.marked = new boolean[graph.V()];
this.count = 0;
this.waitSearch = new Queue<Integer>();
bfs(graph,s);
}
//查询v顶点的所有相同顶点
private void bfs(Graph g,int v){
//v记录以搜索
marked[v] = true;
//让顶点v进入队列
waitSearch.enqueue(v);
while (!waitSearch.isEmpty()) {
//弹出一个待搜索的顶点
Integer wait = waitSearch.dequeue();
for (Integer a : g.adj(v)) {
if (!marked[a]) {
bfs(g, a);
}
}
}
count++;
}
//判断w顶点与s顶点是否相通
public boolean marked(int w){
return marked[w];
}
//获取与顶点s相同的所有顶点总数
public int count(){
return count;
}
}
深度优先求路径
public class DepthFirshPaths {
private boolean[] marked;
private int s;
private int[] edgeTo;
public DepthFirshPaths(Graph G,int s){
this.marked = new boolean[G.V()];
this.s = s;
this.edgeTo = new int[G.V()];
dfs(G, s);
}
private void dfs(Graph G,int v){
//把v标识已搜索
marked[v]= true;
for (Integer w : G.adj(v)) {
//如果w没有搜索
if (!marked[w]){
//到达w的是v
edgeTo[w] = v;
dfs(G,w);
}
}
}
public boolean hasPathTo(int v){
return marked[v];
}
public Stack<Integer> pathTo(int v){
if (!hasPathTo(v)){
return null;
}
//创建栈对象
Stack<Integer> stack = new Stack<>();
for (int x = v;x!=s;x=edgeTo[x]){
stack.push(x);
}
//把起点放入
stack.push(s);
return stack;
}
}