数据结构
数据结构
线性结构:
- 数据元素之间一对一的线性关系
- 可以分为顺序存储结构和链式存储结构
- 顺序存储的线性表称为顺序表 存储元素地址连续
- 链式存储称为链表 存储元素不一定是连续的 节点存放元素及相邻元素地址信息
- 包括数组、队列、链表和栈
非线性结构:
包括二位数组、多维数组、表、树和图
稀疏数组
当一个数组大部分元素为0或同一个值 用稀疏数组保存该数组
| row | col | val
0 | 11 | 11 | 2
1 | 1 | 2 | 1
2 | |2 | 3 | 2
二位数组 转 稀疏矩阵:
- 遍历二维数组,得到有效数据个数sum
- 根据sum创建细稀疏数组 int[sum+1][3]
- 将二维数组的有效数据存入系数矩阵
稀疏数组 转 二维数组: - L先读取稀疏数组的第一行 根据第一行的数据创建二维数组 Arr = int [11] [11]
- 再读取后几行的数据,并赋值给原始的二维数组
队列
- L队列是一个有序列表,可以用数组或是链表实现
- 原则:先入先出
数组模拟队列:
- front为队列首元素的前一位,rear为队列最后一位有效元素下标
- front == rear 队列为空 rear == Size-1 队列为满
public class ArrayQueue{
private int Size;
private int front,rear;
private int[] arr;
public ArrayQueue(int Size){
front = -1;
rear = -1;
this.Size = Size;
this.arr = new int [Size];
}
public boolean isEmpty(){ return rear == front;}
public boolean isFull(){return rear == Size - 1;}
public void add(int n){
if(isFull()){
System.out.println("内存已满,添加请求失败");
return;
}
rear++;
arr[rear] = n;
System.out.println("数据"+n+"添加成功!");
}
public void dele(){
if(isEmpty()){
throw new RuntimeException("内存已空,删除请求失败!");
}
arr[front+1] = 0;
front++;
System.out.println("数据删除成功!");
}
public void showQueue(){
if(isEmpty()){
System.out.println("空队列,无数据!");
return;
}
for (int i = 0; i < arr.length; i++) {
if(arr[i]!=0){
System.out.println("arr["+i+"]"+arr[i]);
}
}
}
public int headQueue(){
if(isEmpty()){
throw new RuntimeException("空队列,无数据!");
}
return arr[front+1];
}
public static void main(String[] args) {
CircleArray arr = new CircleArray(5);
arr.add(1);
arr.add(2);
arr.add(2);
arr.add(2);
arr.showQueue();
}
}
数组模拟环形队列:
- Lfront指向队列的第一个元素下标,rear指向最后一个元素的后一地址(判断为空或是满列)
- (rear + 1)%Size == front -----满列
- rear == front ------空
- 有效元素个数------(rear + Size - front)%Size
import javafx.scene.shape.Circle;
public class CircleArray{
private int Size;
private int front,rear;
private int[] arr;
public CircleArray(int Size){
front = 0;
rear = 0;
this.Size = Size;
this.arr = new int [Size];
}
public boolean isEmpty(){return rear == front;}
public boolean isFull(){ return (rear+1)%Size == front;}
public void add(int n){
if(isFull()){
System.out.println("内存已满,添加请求失败");
return;
}
arr[rear] = n;
rear = (rear + 1)%Size;
System.out.println("数据"+n+"添加成功!");
}
public void dele(){
if(isEmpty()){
throw new RuntimeException("内存已空,删除请求失败!");
}
arr[front+1] = 0;
front = (front+1)%Size;
System.out.println("数据删除成功!");
}
public void showQueue(){
if(isEmpty()){
System.out.println("空队列,无数据!");
return;
}
for (int i = front; i < ((rear + Size - front)%Size); i++) {
System.out.println("arr["+i%Size+"]"+arr[i%Size]);
}
}
public int headQueue(){
if(isEmpty()){
throw new RuntimeException("空队列,无数据!");
}
return arr[front+1];
}
public static void main(String[] args) {
CircleArray arr = new CircleArray(5);
arr.add(1);
arr.add(2);
arr.add(2);
arr.add(2);
System.out.println("首元素为:" + arr.headQueue());
}
}
链表 Linked List
单向链表
- 链表是以节点的方式来存储
- 每个节点包含data,next. next指向下一个节点
- 各个节点不一定是连续存储
带head的单链表模拟水浒排行榜 删除-查找-修改
//一个HeroNode就是一个节点
class HeroNode {
public int no;
public String name;
public String nickName;
public HeroNode next;
public HeroNode(int no, String name, String nickName) {
this.no = no;
this.name = name;
this.nickName = nickName;
}
//重写toString
@Override
public String toString() {
return "HeroNode{" +
"no=" + no +
", name='" + name + '\'' +
", nickName='" + nickName + '\'' +
'}';
}
}
class SingleLinkedList {
private HeroNode head = new HeroNode(0, "", "");
//尾插
public void add(HeroNode heroNode) {
HeroNode temp = head;
//遍历 找到最后的节点
while (true) {
if (temp.next == null) {
break;
} else {
temp = temp.next;
}
}
temp.next = heroNode;
}
//按序号添加
public void addByOrder(HeroNode heroNode) {
HeroNode temp = head;
boolean flag = true;
while(true){
if(temp.next == null){
break;
}
if(temp.next.no > heroNode.no){
break;
}else if(temp.next.no == heroNode.no){
flag = false;
break;
}else{
temp = temp.next;
}
}
if(flag){
heroNode.next = temp.next;
temp.next = heroNode;
}else{
System.out.println("编号"+heroNode.no+"存在,加入失败!!");
}
}
//根据编号no修改信息
public void update(HeroNode heroNode){
HeroNode temp = head;
boolean flag = false;
if(head.next == null){
System.out.println("链表为空");
}
while(true){
if(temp.next==null){
break;
}
if(temp.next.no == heroNode.no){
flag = true;
break;
}
temp = temp.next;
}
if(flag){
temp.next.name = heroNode.name;
temp.next.nickName = heroNode.nickName;
}else{
System.out.println("未找到指定编号,修改失败!!!");
}
}
//根据编号删除节点
public void dele(int num){
boolean flag = false;
HeroNode temp = head;
if (head.next == null){
System.out.println("链表为空");
}
while(true){
if(temp.next==null){
break;
}
if(temp.next.no == num){
flag = true;
break;
}else{
temp = temp.next;
}
}
if(flag){
temp.next = temp.next.next;
}
}
//显示链表
public void show() {
if (head.next == null) {
System.out.println("链表为空!");
return;
}
HeroNode temp = head.next;
while (true) {
if (temp == null) {
System.out.println("链表遍历完成!");
break;
}
System.out.println(temp);
temp = temp.next;
}
}
public static void main(String[] args) {
HeroNode heroNode1 = new HeroNode(1, "武松", "行者");
HeroNode heroNode2 = new HeroNode(2, "宋江", "及时雨");
HeroNode heroNode3 = new HeroNode(3, "吴用", "智多星");
HeroNode heroNode4 = new HeroNode(1, "cc", "帅哥");
SingleLinkedList list = new SingleLinkedList();
list.addByOrder(heroNode1);
list.addByOrder(heroNode3);
list.addByOrder(heroNode2);
list.addByOrder(heroNode2);
list.show();
list.update(heroNode4);
list.show();
list.dele(2);
list.show();
}
}
练习:
- 单链表有效节点的个数
遍历所有节点head.next != null,index++ - 单链表中倒数第k个节点
遍历的到有效节点个数n,遍历到第n-k个节点 - 单链表的反转:头插 新建一个链表的头结点head 遍历旧链表 每遍历一个节点 加入到新链表的head.next
public class ListNode {
int val;
ListNode next;
ListNode() {}
ListNode(int val) { this.val = val; }
ListNode(int val, ListNode next) { this.val = val; this.next = next; }
@Override
public String toString() {
return "ListNode{" +
"val=" + val +
", next=" + next +
'}';
}
}
class Solution {
public static void main(String[] args) {
ListNode head = new ListNode(1,new ListNode(2,new ListNode(3,new ListNode(4,newListNode(5,null)))));
if(head == null || head.next == null){
System.out.println(-1);
}
ListNode cur = head;
ListNode reverseList = new ListNode(0,null);
while(true){
ListNode next = cur.next;
ListNode temp = reverseList.next;
if(next == null){
cur.next = temp;
reverseList.next = cur;
break;
}else{
cur.next = temp;
reverseList.next = cur;
}
cur = next;
}
ListNode res = reverList.next;
System.out.println(reverseList);
}
}
- 从尾到头,打印单链表 方式1 链表反转再打印(破坏单链表的结构) 方式2 利用栈的结构 将各个节点压入栈 先入后出
public class Stack{
public static void main(String[] args){
Stack <String> stack = new Stack();
//入栈
stack.add("jack");
stack.add("tom");
stack.add("smith");
//出栈
while(stack.size()>0){
System.out.println(stack.pop());
}
}
}
- 合并两个有序链表 递归 对比两个链表头 返回头等于或者较小的链 不断递归
class Solution {
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
if(l1 == null){
return l2;
}
if(l2 == null){
return l1;
}
if(l1.val < l2.val){
l1.next = mergeTwoLists(l1.next,l2);
return l1;
}else{
l2.next = mergeTwoLists(l1,l2.next);
return l2;
}
}
}
约瑟夫环
- 1,2,…n个人围一圈,编号为k的人从1开始报数,数到m的人出列;下一位又从1报数,数到m的人出列;知道所有人出列为止,产生一个出编号的序列。
- 数组取模或者用环形列表
(1)创建一个单向环形列表
class CircleSingleLinedList {
//第一个first节点,当前没有编号
private Boy first = new Boy(-1);
//辅助指针cur
Boy cur = null;
//添加节点,构建环形链表
public void addBoy(int nums){
if(nums<1){
System.out.println("输入的值不正确,请重新输入!!!");
return;
}
for (int i = 0; i < nums; i++) {
if(first.getNext()==null){
Boy boy = new Boy(1);
first = boy;
first.setNext(first);
cur = boy;
}else {
Boy boy = new Boy(cur.getNo()+1);
cur.setNext(boy);
boy.setNext(first);
cur = boy;
}
}
}
public void showBoy(){
//判断是否为空
if(first==null){
System.out.println("链表没有任何值!!");
return;
}
Boy cur = first;
while (true){
System.out.println("对象编号为:"+cur.getNo());
if(cur.getNext()==first){
System.out.println("遍历完成!!");
break;
}
cur = cur.getNext();
}
}
}
class Boy{
private int no;
private Boy next;
public Boy(){}
public Boy(int no) {
this.no = no;
}
public int getNo() { return no; }
public void setNo(int no) { this.no = no; }
public Boy getNext() { return next; }
public void setNext(Boy next) { this.next = next; }
}
(2)按指定数出队列,生成一个出圈顺序,n=5共五个人,m=2每次第二个人出圈
- cur指向最后一个节点,first指向第一个节点
- 小孩报数时,让first和cur同时移动m-1次
- first = first.next cur.next = first
//从第k个小孩开始数 count第几个出圈
public void countBoy(int k ,int count){
if(first==null){
System.out.println("没有对象,请输入!!");
}
//确保cur指向最后节点
while(true){
if(cur.getNext()==first){
break;
}else{
cur = cur.getNext();
}
}
//报数前 让first和cur移动k-1次
for (int i = 0; i < k-1; i++) {
first=first.getNext();
cur=cur.getNext();
}
//循环直到圈里只有一个人
while (true){
if(first==cur){
System.out.println("剩余最后的编号为:"+first.getNo());
break;
}
for (int i = 0; i < count-1; i++) {
first=first.getNext();
cur=cur.getNext();
}
System.out.println("对象"+first.getNo()+"出队列!!");
first = first.getNext();
cur.setNext(first);
}
}
(3)测试
class Solution {
public static void main(String[] args) {
CircleSingleLinedList list = new CircleSingleLinedList();
list.addBoy(5);
list.showBoy();
list.countBoy(1,3);
}
}
双向链表
- 遍历与单向链表一样,只是可以向前也可以向后查找
- 添加 默认添加到链表最后 cun.next = new new.pre = temp都要添加
- 修改 与单向链表一样
- 删除 可以实现删除某个自我节点 cur.pre.next = cur.next cur.next.pre = cur.pre
单向环形链表
- 创建第一个节点first,并形成环形
- 创建新的节点时,用临时节点cur指向它,将其加入到环形当中
- 用while循环遍历,直到cur.next==first结束
哈希表
- 散列表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。

模拟一个哈希表
- 有一个公司,当有新的员工来报道时,要求将该员工的信息加入(id,性别,年龄,名字,住址…),当输入该员工的id时,要求查找到该员工的所有信息.
要求:
不使用数据库,速度越快越好=>哈希表(散列)
添加时,保证按照id从低到高插入 [课后思考:如果id不是从低到高插入,但要求各条链表仍是从低到高,怎么解决?
- 使用链表来实现哈希表, 该链表不带表头[即: 链表的第一个结点就存放雇员信息]
- 思路分析并画出示意图
- 代码实现[增删改查(显示所有员工,按id查询)]
import java.util.Scanner;
public class HashTable {
public static void main(String[] args) {
HashTab hashTab = new HashTab(7);
//编写简单的菜单
String key = "";
Scanner scanner = new Scanner(System.in);
while (true){
System.out.println("add: 添加雇员!");
System.out.println("show: 显示成员!");
System.out.println("search: 查找职员!");
System.out.println("exit: 退出系统!");
key = scanner.next();
switch (key){
case "add":
System.out.println("请输入id:");
int id = scanner.nextInt();
System.out.println("请输入姓名:");
String name = scanner.next();
Emp emp = new Emp(id,name);
hashTab.add(emp);
break;
case "search":
System.out.println("请输入id:");
id = scanner.nextInt();
hashTab.search(id);
break;
case "show":
hashTab.show();
break;
case "exit":
scanner.close();
System.exit(0);
break;
default:
break;
}
}
}
}
//创建HashTbale 管理多条链表
class HashTab{
private int size;
private EmpLinkedList[] empLinkedListsArr;
//构造器
public HashTab(int size) {
this.size = size;
empLinkedListsArr = new EmpLinkedList[size];
for (int i = 0; i < size; i++) {
empLinkedListsArr[i] = new EmpLinkedList();
}
}
//添加雇员
public void add(Emp emp){
//根据id得到对象的存放地址
int emLinkedListNo = hash(emp.id);
//添加雇员到对应链表中
empLinkedListsArr[emLinkedListNo].add(emp);
}
//遍历
public void show(){
for (int i = 1; i <= size; i++) {
empLinkedListsArr[i-1].show(i);
}
}
//散列函数(哈希)
public int hash(int id){
return id % size;
}
//查找
public void search(int id){
int tem = hash(id);
Emp emp = empLinkedListsArr[tem].search(id);
if(emp==null){
System.out.println("未找到该对象!");
}else {
System.out.println("在第"+tem+"条链表找到:"+emp.toString());
}
}
}
//职员类
class Emp{
public int id;
public String name;
public Emp next;
public Emp() {
}
public Emp(int id, String name) {
this.id = id;
this.name = name;
}
@Override
public String toString() {
return "Emp{" +
"id=" + id +
", name='" + name + '\'' +
", next=" + next +
'}';
}
}
//链表类
class EmpLinkedList{
private Emp head;
//添加雇员 默认id的分配是从小到大的
public void add(Emp emp){
if (head==null){
head = emp;
return;
}//若不是第一个对象 则循环到链表末尾添加
Emp curEmp = head;
while (true){
if (curEmp.next==null){
break;
}
curEmp = curEmp.next;
}
curEmp.next = emp;
}
//遍历链表
public void show(int no){
if(head==null){
System.out.println("第"+no+"链表为空!!");
return;
}
System.out.print("第"+no+"链表信息为:");
Emp curEmp = head;
while (true){
System.out.print(curEmp.toString()+"\t");
if (curEmp.next==null){
break;
}
curEmp = curEmp.next;
}
System.out.println();
}
//查找雇员
public Emp search(int id){
if(head==null){
return null;
}
Emp curEmp = head;
while(true){
if (curEmp.id==id) {
break;
}
if (curEmp.next==null){
curEmp = null;
}
curEmp = curEmp.next;
}
return curEmp;
}
}
二叉树
- 前序 先输出父节点,再遍历左子树和右子树
- 中序 先遍历左子树,再输出父节点,再遍历右子树
- 后续 先遍历左子树,再遍历右子树,最后输出父节点
小结: 看输出父节点的顺序,就确定是前序,中序还是后序
根左右 1245367
左根右 4251637
左右根 4526731

public class BinaryTree {
public static void main(String[] args) {
Tree tree = new Tree();
Node node1 = new Node(1,"1");
Node node2 = new Node(2,"2");
Node node3 = new Node(3,"3");
Node node4 = new Node(4,"4");
node1.setLeft(node2);
node1.setRight(node3);
node3.setRight(node4);
tree.setRoot(node1);
System.out.println("前序遍历");
tree.preOrder();
System.out.println("中序遍历");
tree.midOrder();
System.out.println("后序遍历");
tree.postOrder();
}
}
class Tree{
private Node root;
public void setRoot(Node root){
this.root = root;
}
//前序遍历
public void preOrder(){
if (this.root!=null){
this.root.preOrder();
}else {
System.out.println("二叉树为空!遍历失败!");
}
}
//前序遍历
public void midOrder(){
if (this.root!=null){
this.root.midOrder();
}else {
System.out.println("二叉树为空!遍历失败!");
}
}
//后序遍历
public void postOrder(){
if (this.root!=null){
this.root.postOrder();
}else {
System.out.println("二叉树为空!遍历失败!");
}
}
}
//创建建Node节点
class Node{
private int no;
private String name;
private Node left;
private Node right;
public Node(int no, String name) {
this.no = no;
this.name = name;
}
public int getNo() { return no; }
public void setNo(int no) { this.no = no; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public Node getLeft() { return left; }
public void setLeft(Node left) { this.left = left; }
public Node getRight() { return right; }
public void setRight(Node right) { this.right = right; }
@Override
public String toString() {
return "Node{" + "no=" + no + ", name='" + name +" }";
}
//前序遍历
public void preOrder() {
System.out.println(this);//先输出父结点
if (this.left!=null){
this.left.preOrder();
}
if (this.right!=null){
this.right.preOrder();
}
}
//中序遍历
public void midOrder() {
if (this.left!=null){
this.left.midOrder();
}
System.out.println(this);
if (this.right!=null){
this.right.midOrder();
}
}
//后续遍历
public void postOrder(){
if (this.left!=null){
this.left.postOrder();
}
if (this.right!=null){
this.right.postOrder();
}
System.out.println(this);
}
}
查找

public class BinaryTree {
public static void main(String[] args) {
Tree tree = new Tree();
Node node1 = new Node(1,"占山");
Node node2 = new Node(2,"里斯");
Node node3 = new Node(3,"王五");
Node node4 = new Node(4,"青湖");
Node node5 = new Node(5,"地藏");
node1.setLeft(node2);
node1.setRight(node3);
node3.setRight(node4);
node3.setLeft(node5);
tree.setRoot(node1);
//前序查找
int no =5;
Node tem = tree.preOrderSearch(no);
if (tem!=null){
System.out.println("前序查找结果:"+"\t"+tem.toString());
}else {
System.out.println("没有找到编号为"+no+"的对象!");
}
//中序查找
tem = tree.midOrderSearch(no);
if (tem!=null){
System.out.println("中序查找结果:"+"\t"+tem.toString());
}else {
System.out.println("没有找到编号为"+no+"的对象!");
}
//后序查找
tem = tree.postOrderSearch(no);
if (tem!=null){
System.out.println("后序查找结果:"+"\t"+tem.toString());
}else {
System.out.println("没有找到编号为"+no+"的对象!");
}
}
}
class Tree{
private Node root;
public void setRoot(Node root){
this.root = root;
}
//前序遍历
public void preOrder(){
if (this.root!=null){
this.root.preOrder();
}else {
System.out.println("二叉树为空!遍历失败!");
}
}
//前序遍历
public void midOrder(){
if (this.root!=null){
this.root.midOrder();
}else {
System.out.println("二叉树为空!遍历失败!");
}
}
//后序遍历
public void postOrder(){
if (this.root!=null){
this.root.postOrder();
}else {
System.out.println("二叉树为空!遍历失败!");
}
}
//前序查找
public Node preOrderSearch(int no){
if(this.root!=null){
return root.preOrderSearch(no);
}
return null;
}
//中序查找
public Node midOrderSearch(int no){
if(this.root!=null){
return root.midOrderSearch(no);
}
return null;
}
//后序查找
public Node postOrderSearch(int no){
if(this.root!=null){
return root.postOrderSearch(no);
}
return null;
}
}
//创建建Node节点
class Node{
private int no;
private String name;
private Node left;
private Node right;
public Node(){}
public Node(int no, String name) {
this.no = no;
this.name = name;
}
public int getNo() { return no; }
public void setNo(int no) { this.no = no; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public Node getLeft() { return left; }
public void setLeft(Node left) { this.left = left; }
public Node getRight() { return right; }
public void setRight(Node right) { this.right = right; }
@Override
public String toString() {
return "Node{" + "no=" + no + ", name='" + name +" }";
}
//前序遍历
public void preOrder() {
System.out.println(this);//先输出父结点
if (this.left!=null){
this.left.preOrder();
}
if (this.right!=null){
this.right.preOrder();
}
}
//中序遍历
public void midOrder() {
if (this.left!=null){
this.left.midOrder();
}
System.out.println(this);
if (this.right!=null){
this.right.midOrder();
}
}
//后续遍历
public void postOrder(){
if (this.left!=null){
this.left.postOrder();
}
if (this.right!=null){
this.right.postOrder();
}
System.out.println(this);
}
//前序查找
public Node preOrderSearch(int no){
//判断当前结点是否为空
System.out.println("前序查找+1");
if(this.getNo()==no){
return this;
}
//判断左节点是否为空
//如果不为空左递归前序查找,找到结点,则返回
Node tem = null;
if (this.left!=null){
tem = this.left.preOrderSearch(no);
}
if (tem!=null){
//找到就返回
return tem;
}
//左遍历未找到则继续向右判断
if (this.right!=null){
tem = this.right.preOrderSearch(no);
}
return tem;
}
//中序查找
public Node midOrderSearch(int no){
//判断当前的左节点是否为空
Node tem = null;
if(this.left!=null){
tem = this.left.midOrderSearch(no);
}
if (tem!=null){
return tem;
}
//没有找到 就和当前节点进行比较 找到则返回
System.out.println("中序查找+1");
if (this.getNo()==no){
return this;
}
//当前节点未找到 则向右递归
if (this.right!=null){
tem = this.right.midOrderSearch(no);
}
return tem;
}
//后序查找
public Node postOrderSearch(int no){
//判断当前的左节点是否为空
Node tem = null;
if (this.left!=null){
tem = this.left.postOrderSearch(no);
}
if (tem!=null){
return tem;
}
//左树未找到 则再右边进行查找
if (this.right!=null){
tem = this.right.postOrderSearch(no);
}
if (tem!=null){
return tem;
}
//左右子树都未找到 则比较当前节点
System.out.println("后序查找+1");
if (this.getNo()==no){
return this;
}
return tem;
}
}
删除
要求:
- 如果删除的节点是叶子节点,则删除该节点。
- 如果删除的节点是非叶子节点,则删除该子树.
思路:

class Teee
//删除
public void del(int no){
if(root!=null){
if (root.getNo()==no){
root = null;
}else {
root.del(no);
}
}else {
System.out.println("空数 不可删除!");
}
}
class Node
//删除
public void del(int no){
//左要删 左不为空
if (this.left!=null && this.left.no==no){
this.left = null;
return;
}
//右要删 右不为空
if (this.right!=null && this.right.no==no){
this.right = null;
return;
}
//先向左递归
if (this.left!=null){
this.left.del(no);
}
//向右递归
if (this.right!=null){
this.right.del(no);
}
}
main
Tree tree = new Tree();
Node node1 = new Node(1,"占山");
Node node2 = new Node(2,"里斯");
Node node3 = new Node(3,"王五");
Node node4 = new Node(4,"青湖");
Node node5 = new Node(5,"地藏");
node1.setLeft(node2);
node1.setRight(node3);
node3.setRight(node4);
node3.setLeft(node5);
tree.setRoot(node1);
tree.preOrder();
tree.del(5);
System.out.println("删除5号");
tree.preOrder();
tree.del(3);
System.out.println("删除3号");
tree.preOrder();
顺序存储二叉树


public class ArrBinaryTree{
public static void main(String[] args) {
int[] arr = {1,2,3,4,5,6,7};
Tree tree = new Tree(arr);
tree.preOrder();
}
}
class Tree{
private int[] arr;
public Tree(int[] arr) {
this.arr = arr;
}
//重载
public void preOrder(){
this.preOrder(0);
}
//前序遍历
public void preOrder(int index){
//数组为空
if (arr==null || arr.length==0){
System.out.println("数组为空!!");
}
//输出这个元素
System.out.println(arr[index]);
//向左递归
if (index*2+1 < arr.length){
preOrder(2*index + 1);
}
if (index*2+2 < arr.length){
preOrder(2*index + 2);
}
}
}
线索化二叉树
- 我们希望充分的利用各个节点(左右不全的节点)的左右指针, 让各个节点可以指向自己的前后节点-线索化二叉树
- 一个结点的前一个结点,称为前驱结点
- 一个结点的后一个结点,称为后继结点
- 线索化后 遍历方式也随之改变 各个节点通过线性方式遍历
import com.sun.media.jfxmediaimpl.HostUtils;
class ThreadedBinaryTree {
public static void main(String[] args) {
Node root = new Node(1,"tom");
Node node1 = new Node(3,"jack");
Node node2 = new Node(6,"smith");
Node node3 = new Node(8,"mary");
Node node4 = new Node(10,"king");
Node node5 = new Node(14,"dim");
root.setLeft(node1);
root.setRight(node2);
node1.setLeft(node3);
node1.setRight(node4);
node2.setLeft(node5);
Tree tree = new Tree();
tree.setRoot(root);
tree.threadedNodes();
//测试10节点
System.out.println(node4.getLeft());
System.out.println(node4.getRight());
//线索化遍历
tree.threadList();
}
}
class Tree {
private Node root;
//为了实现线索化 创建指向当前节点的前驱节点的指针pre
private Node pre = null;
public void setRoot(Node root) {
this.root = root;
}
//重载
public void threadedNodes(){
this.threadedNodes(root);
}
/**
* 中序线索化的方法
* @param node 需要线索化的节点
* leftType==0 表示指向左子树 ==1表示指向前驱节点
* rightType==0 表示指向右子树 ==1表示指向后继节点
*/
public void threadedNodes(Node node){
//节点为空 不能线索化
if (node == null){
return;
}
//线索化左子树
threadedNodes(node.getLeft());
//线索化当前节点
//先处理当前节点的前驱节点
if (node.getLeft() == null){
node.setLeft(pre);
node.setLeftType(1);
}
//处理后继节点
if (pre!=null && pre.getRight()==null){
//让前驱节点的右指针指向当前节点
pre.setRight(node);
//修改前驱节点的右节点类型
pre.setRightType(1);
}
//每处理一个节点后 让当前节点是下一个节点的前驱节点
pre = node;
//线索化右子树
threadedNodes(node.getRight());
}
//遍历中序线索化二叉树
public void threadList() {
//定义变量 存储当前遍历的节点 从root开始
Node node = root;
while (root != null){
//循环找到leftType=1的节点
while (node.getLeftType() == 0){
node = node.getLeft();
}
//打印当前节点
System.out.println(node);
//如果当前节点的右指针指向后继节点 就一直输出
while (node.getRightType() ==1){
node = node.getRight();
System.out.println(node);
}
node = node.getRight();
}
}
}
//创建建Node节点
class Node {
private int no;
private String name;
private Node left;
private Node right;
//leftType==0 表示指向左子树 为1表示指向前驱节点
//rightType==0 表示指向右子树 为1表示指向后继节点
private int leftType;
private int rightType;
public Node(int no, String name) {
this.no = no;
this.name = name;
}
public int getLeftType() {
return leftType;
}
public void setLeftType(int leftType) {
this.leftType = leftType;
}
public int getRightType() {
return rightType;
}
public void setRightType(int rightType) {
this.rightType = rightType;
}
public int getNo() {
return no;
}
public void setNo(int no) {
this.no = no;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Node getLeft() {
return left;
}
public void setLeft(Node left) {
this.left = left;
}
public Node getRight() {
return right;
}
public void setRight(Node right) {
this.right = right;
}
@Override
public String toString() {
return "Node{" + "no=" + no + ", name='" + name + " }";
}
//前序遍历
public void preOrder() {
System.out.println(this);//先输出父结点
if (this.left != null) {
this.left.preOrder();
}
if (this.right != null) {
this.right.preOrder();
}
}
//中序遍历
public void midOrder() {
if (this.left != null) {
this.left.midOrder();
}
System.out.println(this);
if (this.right != null) {
this.right.midOrder();
}
}
//后续遍历
public void postOrder() {
if (this.left != null) {
this.left.postOrder();
}
if (this.right != null) {
this.right.postOrder();
}
System.out.println(this);
}
//前序查找
public Node preOrderSearch(int no) {
//判断当前结点是否为空
System.out.println("前序查找+1");
if (this.getNo() == no) {
return this;
}
//判断左节点是否为空
//如果不为空左递归前序查找,找到结点,则返回
Node tem = null;
if (this.left != null) {
tem = this.left.preOrderSearch(no);
}
if (tem != null) {
//找到就返回
return tem;
}
//左遍历未找到则继续向右判断
if (this.right != null) {
tem = this.right.preOrderSearch(no);
}
return tem;
}
//中序查找
public Node midOrderSearch(int no) {
//判断当前的左节点是否为空
Node tem = null;
if (this.left != null) {
tem = this.left.midOrderSearch(no);
}
if (tem != null) {
return tem;
}
//没有找到 就和当前节点进行比较 找到则返回
System.out.println("中序查找+1");
if (this.getNo() == no) {
return this;
}
//当前节点未找到 则向右递归
if (this.right != null) {
tem = this.right.midOrderSearch(no);
}
return tem;
}
//后序查找
public Node postOrderSearch(int no) {
//判断当前的左节点是否为空
Node tem = null;
if (this.left != null) {
tem = this.left.postOrderSearch(no);
}
if (tem != null) {
return tem;
}
//左树未找到 则再右边进行查找
if (this.right != null) {
tem = this.right.postOrderSearch(no);
}
if (tem != null) {
return tem;
}
//左右子树都未找到 则比较当前节点
System.out.println("后序查找+1");
if (this.getNo() == no) {
return this;
}
return tem;
}
}
霍夫曼树

- 给出一个数列{13,7,8,3,29,6,1},转成一个霍夫曼树
- 从小到大进行排序, 将每一个数据,每个数据都是一个节点 , 每个节点可以看成是一颗最简单的二叉树
- 取出根节点权值最小的两颗二叉树
- 组成一颗新的二叉树, 该新的二叉树的根节点的权值是前面两颗二叉树根节点权值的和
- 再将这颗新的二叉树,以根节点的权值大小 再次排序, 不断重复 1-2-3-4 的步骤,直到数列中,所有的数据都被处理,就得到一颗赫夫曼树
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
class HeapSort {
public static void main(String[] args) {
int arr[] = {13,7,8,3,29,6,1};
preOrder(HuffmanTree(arr));
}
//前序遍历
public static void preOrder(Node root){
if (root != null){
root.preOrder();
}else {
System.out.println("空树,遍历失败!!");
}
}
//创建霍夫曼树
public static Node HuffmanTree(int[] arr){
//遍历arr 将每个元素构建成一个Node 放入ArrayList
List<Node> list = new ArrayList<>();
for (int value: arr) {
list.add(new Node(value));
}
while (list.size()>1) {
//1 排序(从小到大)
Collections.sort(list);
//2 用权值最小的两个点 构建新的树
Node par = new Node(list.get(0).value + list.get(1).value);
par.left = list.get(0);
par.right = list.get(1);
//3 删除处理过的节点
list.remove(0);
list.remove(0);
//4 加入新的树
list.add(par);
}
return list.get(0);
}
}
//创建建Node节点
//为了持续排序Collection 在Node实现Compare接口
class Node implements Comparable<Node>{
int value;
Node left;
Node right;
public Node(int value) {
this.value = value;
}
@Override
public String toString() {
return "Node{" +
"value=" + value;
}
@Override
public int compareTo(Node o) {
return this.value - o.value;
}
//前序遍历
public void preOrder() {
System.out.println(this);//先输出父结点
if (this.left != null) {
this.left.preOrder();
}
if (this.right != null) {
this.right.preOrder();
}
}
}
霍夫曼编码


import java.util.*;
class HeapSort {
public static void main(String[] args) {
String content = "i like like like java do you like a java";
byte[] bytes = content.getBytes();
List<Node> list = getNodes(bytes);
Node root = Huffmantree(list);
root.preOrder();
System.out.println("霍夫曼编码:");
getCodes(root,"",string);
System.out.println(huffmanCodes);
}
//将编码表存放为 Map<Byte,String>的形式 32—>01 0->0011
static Map<Byte,String> huffmanCodes = new HashMap<>();
//生成的霍夫曼编码 需要不断拼接 定义一个StringBuilder存储节点路径
static StringBuilder string = new StringBuilder();
//生成霍夫曼树对应的霍夫曼编码
/**
* 得到传入节点为node的所有叶子节点的霍夫曼编码 存放到huffmanCodes中
* @param node 传入节点
* @param code 路径 左子节点0 右子节点1
* @param string 拼接路径
*/
public static void getCodes(Node node,String code,StringBuilder string){
StringBuilder string1 = new StringBuilder(string);
//每个叶子节点 用一个string1保存这一路径的值
string1.append(code);
if (node.data == null){
//不是叶子节点 则不断递归
getCodes(node.left,"0",string1);
getCodes(node.right,"1",string1);
}else {
//找到叶子节点 保存编码
huffmanCodes.put(node.data,string1.toString());
}
}
//前序遍历
public static void preOrder(Node root){
if (root != null){
root.preOrder();
}else {
System.out.println("空树,遍历失败!!");
}
}
//接受字节数组
public static List<Node> getNodes(byte[] bytes){
//创建list集合 存放数据和权重
List<Node> list = new ArrayList();
//利用map来统计
Map<Byte,Integer> map = new HashMap<>();
for (byte b : bytes) {
Integer count = map.get(b);
if (count == null){
//map还没有这个字符
map.put(b,1);
}else {
//有这个字符 则权重+1
map.put(b,count+1);
}
}
//把map的键值对转换成Node对象 存储到list中
//map的遍历
for (Map.Entry<Byte,Integer> entry : map.entrySet()) {
list.add(new Node(entry.getKey(), entry.getValue()));
}
return list;
}
//通过list创建霍夫曼树
public static Node Huffmantree(List<Node> list){
while (list.size() > 1){
//排序
Collections.sort(list);
//取出最小的两个元素
Node par = new Node(null,list.get(0).weight+list.get(1).weight);
par.left = list.get(0);
par.right = list.get(1);
//删除处理过的节点
list.remove(0);
list.remove(0);
//4 加入新的树
list.add(par);
}
return list.get(0);
}
}
/**
* data 数据本身 ‘a’‘b’
* weight 数据权重 在句子中出现的次数
*/
class Node implements Comparable<Node>{
Byte data;
int weight;
Node left;
Node right;
public Node(Byte data, int weight) {
this.data = data;
this.weight = weight;
}
@Override
public String toString() {
return "Node{" +
"data=" + data +
", weight=" + weight +
'}';
}
@Override
public int compareTo(Node o) {
return this.weight - o.weight;
}
//前序遍历
public void preOrder() {
System.out.println(this);//先输出父结点
if (this.left != null) {
this.left.preOrder();
}
if (this.right != null) {
this.right.preOrder();
}
}
}
二叉排序树
- 二叉排序树:BST: (Binary Sort(Search) Tree), 又名二叉搜索树,对于二叉排序树的任何一个非叶子节点,要求左子节点的值比当前节点的值小,右子节点的值比当前节点的值大。
- 中序遍历的结果,即为从小到大排序
- 查找 插入 删除 效率都很高
创建、遍历、删除 其中删除比较复杂 
-
删除叶子节点 (比如:2, 5, 9, 12)

-
删除只有一颗子树的节点 (比如:1)

-
删除有两颗子树的节点. (比如:7, 3,10 )

import java.util.*;
class BinarySortTree {
public static void main(String[] args) {
int[] arr = {7,3,10,12,5,1,9};
Tree tree = new Tree();
//循环添加
for (int i = 0; i < arr.length; i++) {
tree.add(new Node(arr[i]));
}
tree.midOrder();
tree.dele(10);
System.out.println("删除:");
tree.midOrder();
}
//中序遍历
public static void midOrder(Node root){
if (root != null){
root.midOrder();
}else {
System.out.println("空树,遍历失败!!");
}
}
}
class Tree{
private Node root;
public Tree(Node root) {
this.root = root;
}
public Tree() {
}
//添加
public void add(Node node){
if (root == null){
root = node;
}else {
root.add(node);
}
}
//查找要删除的节点
public Node search(int value){
if (root == null){
return null;
}else {
return root.search(value);
}
}
//查找要删除节点的父节点
public Node searchParent(int value){
if (root == null){
return null;
}else {
return root.searchParent(value);
}
}
//查找以该节点为根节点的子树最小值
public int treeMin(Node node){
while (node.left != null){
node = node.left;
}
return node.value;
}
//删除
public void dele(int value){
if (root == null){
return;
}else {
//1.先寻找要删除的节点
Node targetNode = root.search(value);
if (targetNode == null){
return;
}
//只有一个根节点 并且是要删除的节点
if (root.left == null && root.right == null){
root = null;
}
//2.寻找要删除节点的父节点
Node parent = root.searchParent(value);
//3.判断要删除的是否是叶子节点
if (targetNode.left == null && targetNode.right == null){
//判断targetNode是左节点还是右节点
if (parent != null && parent.left != null && parent.left.value ==value){
parent.left = null;
}else {
parent.right = null;
}
//删除节点有两颗子树
}else if (targetNode.left != null && targetNode.right !=null){
int minValue = treeMin(targetNode.right);
dele(minValue);
targetNode.value = minValue;
} else{//删除节点只有一个子树
//只有左子树
if (targetNode.left != null){
if (parent != null) {
//targerNode是parent的左子节点
if (parent.left.value == value) {
parent.left = targetNode.left;
} else { //targerNode是parent的右子节点
parent.right = targetNode.left;
}
}else {
root = targetNode.left;
}
}else { //只有右子树
if (parent != null) {
//targerNode是parent的左子节点
if (parent.left.value == value) {
parent.left = targetNode.right;
} else { //targerNode是parent的右子节点
parent.right = targetNode.right;
}
}else {
root = parent.right;
}
}
}
}
}
//中序遍历
public void midOrder(){
if (root == null){
System.out.println("根节点为空,遍历失败!");
}else {
root.midOrder();
}
}
}
class Node {
int value;
Node left;
Node right;
public Node(int value) {
this.value = value;
}
@Override
public String toString() {
return "Node{" +
"value=" + value +
'}';
}
//查找要删除的节点
public Node search(int value){
if (this.value == value){
return this;
}else if (this.value > value){
if (this.left == null){
return null;
}else {
return this.left.search(value);
}
}else {
if (this.right == null){
return null;
}else {
return this.right.search(value);
}
}
}
//查找要删除的节点的父节点
public Node searchParent(int value){
if ((this.left != null && this.left.value == value) ||
(this.right != null && this.right.value == value)){
return this;
}else {
if (this.value <= value && this.right != null){
return this.right.searchParent(value);
}else if (this.value > value && this.left != null){
return this.left.searchParent(value);
}else {
return null;
}
}
}
//添加节点
public void add(Node node){
if (node == null){
return;
}
//判断传入节点和当前子树根节点的值关系
if (node.value < this.value){
if (this.left == null){
this.left = node;
}else {
this.left.add(node);
}
}else {
if (this.right == null){
this.right = node;
}else {
this.right.add(node);
}
}
}
//中序遍历
public void midOrder() {
if (this.left != null) {
this.left.midOrder();
}
System.out.println(this);
if (this.right != null) {
this.right.midOrder();
}
}
}
平衡二叉树(AVL)


class AVLTree {
public static void main(String[] args) {
int[] arr = {10,11,7,7,8,9};
Tree tree = new Tree();
for (int i = 0; i < arr.length; i++) {
tree.add(new Node(arr[i]));
}
tree.midOrder();
System.out.println("平衡前:");
System.out.println("高度为:" + tree.root.height());
System.out.println("左树高度:"+tree.root.leftHeight());
System.out.println("右树高度:"+tree.root.rightHeight());
}
}
class Tree{
public Node root;
public Tree(Node root) {
this.root = root;
}
public Tree() {
}
//添加
public void add(Node node){
if (root == null){
root = node;
}else {
root.add(node);
}
}
//查找要删除的节点
public Node search(int value){
if (root == null){
return null;
}else {
return root.search(value);
}
}
//查找要删除节点的父节点
public Node searchParent(int value){
if (root == null){
return null;
}else {
return root.searchParent(value);
}
}
//查找以该节点为根节点的子树最小值
public int treeMin(Node node){
while (node.left != null){
node = node.left;
}
return node.value;
}
//删除
public void dele(int value){
if (root == null){
return;
}else {
//1.先寻找要删除的节点
Node targetNode = root.search(value);
if (targetNode == null){
return;
}
//只有一个根节点 并且是要删除的节点
if (root.left == null && root.right == null){
root = null;
}
//2.寻找要删除节点的父节点
Node parent = root.searchParent(value);
//3.判断要删除的是否是叶子节点
if (targetNode.left == null && targetNode.right == null){
//判断targetNode是左节点还是右节点
if (parent != null && parent.left != null && parent.left.value ==value){
parent.left = null;
}else {
parent.right = null;
}
//删除节点有两颗子树
}else if (targetNode.left != null && targetNode.right !=null){
int minValue = treeMin(targetNode.right);
dele(minValue);
targetNode.value = minValue;
} else{//删除节点只有一个子树
//只有左子树
if (targetNode.left != null){
if (parent != null) {
//targerNode是parent的左子节点
if (parent.left.value == value) {
parent.left = targetNode.left;
} else { //targerNode是parent的右子节点
parent.right = targetNode.left;
}
}else {
root = targetNode.left;
}
}else { //只有右子树
if (parent != null) {
//targerNode是parent的左子节点
if (parent.left.value == value) {
parent.left = targetNode.right;
} else { //targerNode是parent的右子节点
parent.right = targetNode.right;
}
}else {
root = parent.right;
}
}
}
}
}
//中序遍历
public void midOrder(){
if (root == null){
System.out.println("根节点为空,遍历失败!");
}else {
root.midOrder();
}
}
}
class Node {
int value;
Node left;
Node right;
public Node(int value) {
this.value = value;
}
@Override
public String toString() {
return "Node{" +
"value=" + value +
'}';
}
//左旋方法
private void leftRotate(){
//创建新节点 以当前根节点的值
Node newNode = new Node(value);
//把新的节点的左子树 设置为当前节点的左子树
newNode.left = this.left;
//把新节点的右子树 设置为当前节点的右子树的左子树
newNode.right = this.right.left;
//把当前节点的值替换成右子节点的值
value = right.value;
//把当前节点的右子树 设置为当前节点右子树的右子树
this.right = this.right.right;
//把当前节点的左子树 设置为新的节点
this.left = newNode;
}
//右旋方法
private void rightRotate(){
Node newNode = new Node(value);
newNode.right = right;
newNode.left = left.right;
value = left.value;
left = left.left;
right = newNode;
}
//返回当前节点左子树高度
public int leftHeight(){
if (this.left == null){
return 0;
}else {
return this.left.height();
}
}
//返回当前节点右子树高度
public int rightHeight(){
if (this.right == null){
return 0;
}else {
return this.right.height();
}
}
//返回以当前节点为根节点的树高度
public int height(){
return Math.max(this.left == null ? 0 : this.left.height(),
this.right == null ? 0 : this.right.height()) + 1;
}
//查找要删除的节点
public Node search(int value){
if (this.value == value){
return this;
}else if (this.value > value){
if (this.left == null){
return null;
}else {
return this.left.search(value);
}
}else {
if (this.right == null){
return null;
}else {
return this.right.search(value);
}
}
}
//查找要删除的节点的父节点
public Node searchParent(int value){
if ((this.left != null && this.left.value == value) ||
(this.right != null && this.right.value == value)){
return this;
}else {
if (this.value <= value && this.right != null){
return this.right.searchParent(value);
}else if (this.value > value && this.left != null){
return this.left.searchParent(value);
}else {
return null;
}
}
}
//添加节点
public void add(Node node){
if (node == null){
return;
}
//判断传入节点和当前子树根节点的值关系
if (node.value < this.value){
if (this.left == null){
this.left = node;
}else {
this.left.add(node);
}
}else {
if (this.right == null){
this.right = node;
}else {
this.right.add(node);
}
}
//左旋
if (rightHeight() - leftHeight() > 1){
if (right != null && right.leftHeight() > right.rightHeight()){
//右子树的 左子树的高度大于其右子树高度 先右旋
right.rightRotate();
//再左旋
leftRotate();
}else {
//直接进行左旋
leftRotate();
}
return;//必须要
}
//右旋
if (leftHeight() - rightHeight() > 1){
if (left != null && left.rightHeight() > left.leftHeight()){
//左子树的 右子树的高度大于其左子树高度 先左旋
left.leftRotate();
//再右旋
rightRotate();
}else {
//直接进行右旋
rightRotate();
}
}
}
//中序遍历
public void midOrder() {
if (this.left != null) {
this.left.midOrder();
}
System.out.println(this);
if (this.right != null) {
this.right.midOrder();
}
}
}
多叉树和B树


- B-tree树即B树,B即Balanced,平衡的意思,也符合二叉排序树的特性。有人把B-tree翻译成B-树,容易让人产生误解。会以为B-树是一种树,而B树又是另一种树。实际上,B-tree就是指的B树。





图




创建图解

- 图的遍历
- 深度优先搜索(Depth First Search) --DFS
- 每次都在访问完当前结点后首先访问当前结点的第一个邻接结点
- 访问初始结点v,并标记结点v为已访问。
- 查找结点v的第一个邻接结点w。
- 若w存在,则继续执行4,如果w不存在,则回到第1步,将从v的下一个结点继续。
- 若w未被访问,对w进行深度优先遍历递归(即把w当做另一个v,然后进行步骤123)。
- 查找结点v的w邻接结点的下一个邻接结点,转到步骤3。
- 广度优先搜索(Broad First Search)–BFS
- 类似于一个分层搜索的过程,广度优先遍历需要使用一个队列以保持访问过的结点的顺序,以便按这个顺序来访问这些结点的邻接结点
- 访问初始结点v并标记结点v为已访问。
- 结点v入队列
- 当队列非空时,继续执行,否则算法结束。
- 出队列,取得队头结点u。
- 查找结点u的第一个邻接结点w。
- 若结点u的邻接结点w不存在,则转到步骤3;否则循环执行以下三个步骤:
6.1 若结点w尚未被访问,则访问结点w并标记为已访问。
6.2 结点w入队列
6.3 查找结点u的继w邻接结点后的下一个邻接结点w,转到步骤6。
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
class Graph{
private ArrayList<String> vertexList;//顶点集合
private int[][] edges;//图相应矩阵
private int numOfEdges;//边的数目
private boolean[] isVisited;//标记某个节点是否被访问
public static void main(String[] args) {
int n = 5;
String vertexValue[] = {"A","B","C","D","E"};
//创建图对象
Graph graph = new Graph(n);
//添加顶点
for (String vertex : vertexValue){
graph.insertVertex(vertex);
}
//添加边 A-B A-C B-C B-D B-E
graph.insertEdge(0,1,1);
graph.insertEdge(0,2,1);
graph.insertEdge(1,2,1);
graph.insertEdge(1,3,1);
graph.insertEdge(1,4,1);
graph.show();
//dfs深度遍历
System.out.println("深度优先!");
graph.dfs();
System.out.println("");
//bfs广度优先
System.out.println("广度优先!");
graph.bfs();
}
//构造器
public Graph(int n) {
//初始化矩阵和vertexList
edges = new int[n][n];
vertexList = new ArrayList<String>(n);
}
//深度优先遍历
public void dfs(int i,boolean[] isVisited){
//首先输出该节点
System.out.print(getValueByIndex(i) + "->");
isVisited[i] = true;
//查找第一个临接节点
int w = getFirstNeighbor(i);
while (w != -1){//找到第一个临接节点
if (!isVisited[w]){
dfs(w,isVisited);
}//若第一个节点已被访问过
w= getNextNeighbor(i,w);
}
}
//广度优先遍历
private void bfs(boolean[] isVisited, int i) {
int u ; // 表示队列的头结点对应下标
int w ; // 邻接结点 w
// 队列,记录结点访问的顺序
LinkedList queue = new LinkedList();
//访问结点,输出结点信息
System.out.print(getValueByIndex(i) + "=>");
//标记为已访问
isVisited[i] = true;
//将结点加入队列
queue.addLast(i);
while( !queue.isEmpty()) {
//取出队列的头结点下标
u = (Integer)queue.removeFirst();
//得到第一个邻接结点的下标 w
w = getFirstNeighbor(u);
while(w != -1) {//找到
// 是否访问过
if(!isVisited[w]) {
System.out.print(getValueByIndex(w) + "=>");
//标记已经访问
isVisited[w] = true;
// 入队
queue.addLast(w);
//
}
//以 u 为前驱点,找 w 后面的下一个邻结点
w = getNextNeighbor(u, w);
//体现出我们的广度优先
}
}
}
//遍历所有的结点,都进行广度优先搜索
public void bfs() {
isVisited = new boolean[vertexList.size()];
for(int i = 0; i < getNumOfVertex(); i++) {
if(!isVisited[i]) {
bfs(isVisited,i);
}
}
}
//对dfs重载 遍历所有的节点 并进行
public void dfs(){
isVisited = new boolean[vertexList.size()];
for (int i = 0; i < getNumOfVertex(); i++) {
if (!isVisited[i]){
dfs(i,isVisited);
}
}
}
//得到index节点第1个临接节点的下标
public int getFirstNeighbor(int index){
for (int i = 0; i < vertexList.size(); i++) {
if (edges[index][i] > 0){
return i;
}
}
return -1;
}
//根据前一个临接节点下标v1 获取下一个临接节点下标
public int getNextNeighbor(int index,int v1){
for (int i = v1 + 1; i < vertexList.size(); i++) {
if (edges[index][v1] > 0){
return i;
}
}
return -1;
}
//图的常用方法
//返回节点个数
public int getNumOfVertex(){
return vertexList.size();
}
//返回边的数目
public int getNumOfEdges(){
return numOfEdges;
}
//返回下标i对应的节点
public String getValueByIndex(int i){
return vertexList.get(i);
}
//返回v1 v2的权值
public int getWEeight(int v1,int v2){
return edges[v1][v2];
}
//显示图对应的矩阵
public void show(){
for (int[] line : edges){
System.out.println(Arrays.toString(line));
}
}
//插入节点
public void insertVertex(String vertex){
vertexList.add(vertex);
}
//添加边
/**
*
* @param v1 点的下标 即第1个顶点
* @param v2 点的下标 即第2个顶点
* @param weight 边的权值
*/
public void insertEdge(int v1,int v2,int weight){
edges[v1][v2] = weight;
edges[v2][v1] = weight;
numOfEdges++;
}
}
栈
- 英文为stack
- 先入后出的原则,是一个有序列表
- push入栈 pop出栈
用数组模拟一个栈
public class ArrayStackDemo{
public static void main(String[] args) {
ArrayStack arrayStack = new ArrayStack(10);
arrayStack.push(1);
arrayStack.push(2);
arrayStack.push(3);
arrayStack.list();
}
}
class ArrayStack{
private int maxSize;//栈大小
private int[] stack;//数组模拟栈
private int top=-1;//栈顶 初始化-1
//构造器
public ArrayStack(int maxSize) {
this.maxSize = maxSize;
stack = new int[this.maxSize];
}
//判断边界
public boolean isFull(){
return top == maxSize-1;
}
private boolean isEmpty(){
return top == -1;
}
//push入栈
public void push(int value){
if(isFull()){
System.out.println("栈满");
return;
}
top++;
stack[top]=value;
}
//pop出栈
public int pop(){
if(isEmpty()){
throw new RuntimeException("栈空,没有数据");
}
return stack[top--];
}
//遍历
public void list(){
if(isEmpty()){
System.out.println("空,没有数据");
return;
}
//从栈顶开始显示
for (int i = top; i >= 0; i--) {
System.out.println("stack["+i+"]="+stack[i]);
}
}
}
栈-计算表达式(中缀表达式)
- 创建两个栈 :numStack存放数据;operStack存放运算符
- 数字直接入栈
- 运算符-若栈空 直接入栈;栈非空 则比较当前和栈顶符号优先级:若当前符号优先级小于等于栈顶优先级,则numStack弹出两个数据,peroStack出栈栈顶符号,进行运算,得到的结果在numStack入栈,将当前符号入栈;若当前符号优先级更大,则直接peroStack入栈。、、
- 计算式扫描完毕。顺序得从数据和符号中出栈并计算
- 数字栈只有一个数据时,就是所求结果
public class ArrayStackDemo{
public static void main(String[] args) {
ArrayStack numStack = new ArrayStack(10);
ArrayStack operStack = new ArrayStack(10);
String expression = "20+20/20";
String str = "";
//得到表达式的每一个字符
for (int i = 0; i < expression.length(); i++) {
char ch = expression.charAt(i);
//是否为运算符
if(operStack.isOper(ch)){
if(!operStack.isEmpty()){
//不为空 比较优先级
if(operStack.priority(ch) <= operStack.priority(operStack.peek())){
int num1 = numStack.pop();
int num2 = numStack.pop();
int oper = operStack.pop();
int res = numStack.cal(num1,num2,oper);
numStack.push(res);
operStack.push(ch);
}else {
operStack.push(ch);
}
}else {
operStack.push(ch);
}
}//数字 则直接入栈 注意 可以使多位数
else {
str+=ch;
if(i==expression.length()-1){
numStack.push(Integer.parseInt(str));
break;
}
if(operStack.isOper(expression.charAt(i+1))){
numStack.push(Integer.parseInt(str));
str = "";
}
}
}
//扫描完毕,按顺序执行栈中元素
while (true){
if(operStack.isEmpty()){
break;
}
int num1 = numStack.pop();
int num2 = numStack.pop();
int oper = operStack.pop();
int res = numStack.cal(num1,num2,oper);
numStack.push(res);
}
System.out.println("计算结果为:"+numStack.pop()+"!!!!");
}
}
class ArrayStack{
private int maxSize;//栈大小
private int[] stack;//数组模拟栈
private int top=-1;//栈顶 初始化-1
//构造器
public ArrayStack() {
}
public ArrayStack(int maxSize) {
this.maxSize = maxSize;
stack = new int[this.maxSize];
}
//判断边界
public boolean isFull(){
return top == maxSize-1;
}
public boolean isEmpty(){
return top == -1;
}
//push入栈
public void push(int value){
if(isFull()){
System.out.println("栈满");
return;
}
top++;
stack[top]=value;
}
//pop出栈
public int pop(){
if(isEmpty()){
throw new RuntimeException("栈空,没有数据");
}
return stack[top--];
}
public int peek(){
return stack[top];
}
//遍历
public void list(){
if(isEmpty()){
System.out.println("空,没有数据");
return;
}
//从栈顶开始显示
for (int i = top; i >= 0; i--) {
System.out.println("stack["+i+"]="+stack[i]);
}
}
//优先级
public int priority(int oper){
if(oper == '*' || oper == '/'){
return 1;
}else if(oper == '+' || oper == '-'){
return 0;
}else {
return -1;
}
}
//判断运算符
public boolean isOper(char val){
return val=='-' || val=='*' || val=='+' ||val=='/';
}
//判断数字
public boolean isNum(char val){
return 0 < val && val < 10;
}
//计算
public int cal(int num1,int num2,int oper){
int res = 0;
switch (oper){
case '+':
return num1+num2;
case '-':
return num2-num1;
case '*':
return num1*num2;
case '/':
return num2/num1;
default:
return res;
}
}
}
递归
简单的说:就是自己调用自己,每次传入不同变量
- 程序每执行一个方法,都会开辟一个独立的空间(栈)
- 每个空间的数据(局部变量)是独立的
- 如果方法中使用的是引用类型变量 就会共享该引用类型的数据
- 递归必须向退出递归的条件逼近 否则无限循环
- 谁调用 返回谁
//阶乘
public statci int text(int n){
if(n==1){
return 1;
} else{
return text(n-1)*n;
}
}
中缀转后缀表达式
-
前缀表达式 运算符位于操作符之前
(3+4)5-6 -->-+3456
从右至左扫描,数字直接入栈,遇到运算符时弹出两个数进行计算,并将结果入栈。重复直到表达式最左边得到结果 -
中缀表达式
(3+4)*5-6 判断运算符优先级运算,对于计算机来说不好操作,一般都是转换成其他表达式来操作的 -
后缀表达式(逆波兰表达式) 运算符位于操作数之后
(3+4)5-6 -->34+56-
从左至右扫描,遇到数字将数字入栈,遇到运算符时弹出两个数做运算,并将结果入栈。重复直到扫描完成得到结果 -
中缀转后缀“1+((2+3)×4)-5”–>“1 2 3 + 4 × + 5 –”
- 初始化两个栈:运算符栈s1和储存中间结果的栈s2;
- 从左至右扫描中缀表达式;
- 遇到操作数时,将其压s2;
- 遇到运算符时,比较其与s1栈顶运算符的优先级:
(1)如果s1为空,或栈顶运算符为左括号“(”,则直接将此运算符入栈;
(2)否则,若优先级比栈顶运算符的高,也将运算符压入s1;
(3)否则,将s1栈顶的运算符弹出并压入到s2中,再次转到(4-1)与s1中新的栈顶运算符相比较; - 遇到括号时:
(1) 如果是左括号“(”,则直接压入s1
(2) 如果是右括号“)”,则依次弹出s1栈顶的运算符,并压入s2,直到遇到左括号为止,此时将这一对括号丢弃 - 重复步骤2至5,直到表达式的最右边
- 将s1中剩余的运算符依次弹出并压入s2
- 依次弹出s2中的元素并输出,结果的逆序即为中缀表达式对应的后缀表达式
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
public class ArrayStackDemo {
public static void main(String[] args) {
//定义逆波兰表达式
String input = "1+((2+3)*4)-5";
String sufficExpression = transExpression(input);
System.out.println(sufficExpression);
int res = getRes(sufficExpression);
System.out.println("计算结果为"+res+"!!!");
}
//中缀转后缀 为了不用逆序输出 存储栈换成List结构
public static String transExpression(String input) {
List<String> list = new ArrayList<>();
Stack<String> operStack = new Stack<>();
String str = "";
for (int i = 0; i < input.length(); i++) {
char tem = input.charAt(i);
//判断数字 考虑多位
if (tem <= 57 && tem >= 48) {
str += tem;
if (i != input.length() - 1) {
if (input.charAt(i + 1) > 57 || input.charAt(i + 1) < 48) {
list.add(str);
str = "";
}
} else {
list.add(str);
}
}
//判断符号位 遇到运算符
else if (tem == '+' || tem == '-' || tem == '*' || tem == '/') {
while (true){
if(operStack.size()!=0 &&
priority(tem)<priority(operStack.peek().charAt(0))){
list.add(operStack.pop());
}else {
operStack.push(tem + "");
break;
}
}
}
//左括号直接入栈
else if (tem == '(') {
operStack.push(tem + "");
}
//右括号 弹出元素 直到遇到左括号为止 丢掉这对括号
else if (tem == ')') {
while (operStack.peek().charAt(0) != '(') {
list.add(operStack.pop());
}
operStack.pop();
}
else{
throw new RuntimeException("符号有误!请重新输入!!");
}
}
//剩余运算符加入到列表中
while (operStack.size()!=0){
list.add(operStack.pop());
}
StringBuffer res = new StringBuffer();
for (String s:list) {
res.append(s+" ");
}
return res.toString();
}
//计算逆波兰表达式
//从左至右扫描,遇到数字将数字入栈,遇到运算符时弹出两个数做运算,并将结果入栈。重复直到扫描完成得到结果
public static int getRes(String sufficExpression){
Stack <String> stack = new Stack<>();
String [] split = sufficExpression.split(" ");
for (String val:split) {
if(val.matches("\\d+")){//匹配多位数
stack.push(val);
}else {
int res = 0;
int num2 = Integer.parseInt(stack.pop());
int num1 = Integer.parseInt(stack.pop());
switch (val.charAt(0)){
case '+':
res = num1 + num2;
break;
case '-':
res = num1 - num2;
break;
case '*':
res = num1 * num2;
break;
case '/':
res = num1 / num2;
break;
}
stack.push(String.valueOf(res));
}
}
return Integer.parseInt(stack.pop());
}
public static int priority(char c){
int res = 0;
switch (c){
case '+':res = 0;break;
case '-':res = 0;break;
case '*':res = 1;break;
case '/':res = 1;break;
}
return res;
}
}
八皇后问题
-
8X8的格子上摆放8个皇后,使其不能相互攻击(任意两个不能处于同一行、同一列或同一斜线),问有多少种摆法?
-
第一个皇后先放第一行第一列
-
第二个皇后放在第二行第一列、然后判断是否OK, 如果不OK,继续放在第二列、第三列、依次把所有列都放完,找到一个合适
-
继续第三个皇后,还是第一列、第二列……直到第8个皇后也能放在一个不冲突的位置,算是找到了一个正确解
-
当得到一个正确解时,在栈回退到上一个栈时,就会开始回溯,即将第一个皇后,放到第一列的所有正确解,全部得到.
-
然后回头继续第一个皇后放第二列,后面继续循环执行 1,2,3,4的步骤
-
说明:理论上应该创建一个二维数组来表示棋盘,但是实际上可以通过算法,用一个一维数组即可解决问题. arr = {0 , 4, 7, 5, 2, 6, 1, 3} //对应arr 下标 表示第几行和第几个皇后,arr[i] = val , 表示第i+1个皇后,放在第i+1行的第val+1列
public class TextDemo {
//8X8的map表示棋盘
int max = 8;
int [] map = new int[max];
int count = 0;
public static void main(String[] args) {
TextDemo queue = new TextDemo();
queue.put(0);
System.out.println(queue.count);
}
private void put(int n){
if(n==max){
print();
count++;
return;
}
//依次从第一列放入 检查冲突
for (int i = 0; i < max; i++) {
map[n] = i;
if(check(n)){
put(n+1);
}
}
}
//检查第n个皇后是否冲突
private boolean check(int n){
for (int i = 0; i < n; i++) {
//判断是否在一列 或 是否在一条斜线
if(map[i]==map[n] || Math.abs(n-i) == Math.abs(map[n]-map[i])){
return false;
}
}
return true;
}
private void print(){
for (int i = 0; i < max; i++) {
System.out.print(map[i]);
}
System.out.println();
}
}
迷宫问题
public class ArrayStackDemo {
public static void main(String[] args) {
//二维数组 模拟迷宫
//地图
int [][] map = new int[8][7];
//1表示墙 上下全部置为1
for (int i = 0; i < map.length; i++) {
map[i][0]= 1;
map[i][map[0].length-1] = 1;
}
for (int i = 0; i < map[0].length; i++) {
map[0][i] = 1;
map[map.length-1][i] = 1;
}
//路径障碍 设置为1
map[3][1] = 1;
map[3][2] = 1;
findWay(map,1,1);
for (int i = 0; i < map.length; i++) {
for (int j = 0; j < map[0].length; j++) {
System.out.print(map[i][j]+" ");
}
System.out.println();
}
}
//约定map[i][j]=0未走 1表示墙 2表示可以走 3表示走过但是不通
//策略:下右上左
//map[6][5]说明通路找到
/**
*
* @param map 表示迷宫地图
* @param i 行
* @param j 列 开始的位置
* @return 若找到通路 返回true 否则返回false
*/
public static boolean findWay(int[][]map,int i,int j){
if(map[6][5] == 2){
return true;
}else {
if (map[i][j] == 0){
map[i][j] = 2;
if(findWay(map,i+1,j)){//向下走
return true;
}else if(findWay(map,i,j+1)){//向右走
return true;
}else if(findWay(map,i-1,j)){//上
return true;
}else if (findWay(map,i,j-1)){//左
return true;
}else {//该点走不通
map[i][j] = 3;
return false;
}
}else {
return false;
}
}
}
}
典型算法
1.二分查找算法(非递归)
- 二分查找法只适用于从有序的数列中进行查找(比如数字和字母等),将数列排序后再进行查找
- 二分查找法的运行时间为对数时间O(㏒₂n) ,即查找到需要的目标位置最多只需要㏒₂n步,假设从[0,99]的队列(100个数,即n=100)中寻到目标数30,则需要查找步数为㏒₂100 , 即最多需要查找7次( 2^6 < 100 < 2^7)
class Algorithm{
public static void main(String[] args) {
int[] arr = {1,3,8,10,11,67,100};
System.out.println(binarySearch(arr,1));
}
/**
* 二分查找(非递归)
* @param arr 待查找的数组
* @param target 查找的目标值
* @return -1表示没有找到
*/
public static int binarySearch(int[] arr,int target){
int left = 0;
int right = arr.length - 1;
while (left <= right){
int mid = (right + left) / 2;
if (arr[mid] == target){
return mid;
}else if (arr[mid] > target){
//向左边继续查找
right = mid - 1;
}else {
//向右继续查找
left = mid + 1;
}
}
return -1;
}
}
2.分治算法
- 分治法是一种很重要的算法。字面上的解释是“分而治之”,就是把一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题……直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并。这个技巧是很多高效算法的基础,如排序算法(快速排序,归并排序),傅立叶变换(快速傅立叶变换)……
- 可以解决二分搜索、大整数乘法、棋盘覆盖、合并排序、快速排序、线性时间选择、最接近点对问题、循环赛日程表、汉诺塔
- 分解:将原问题分解为若干个规模较小,相互独立,与原问题形式相同的子问题
- 解决:若子问题规模较小而容易被解决则直接解,否则递归地解各个子问题
- 合并:将各个子问题的解合并为原问题的解。
汉诺塔问题
- 若盘数n=1,直接从A->C
- 若n>=2,总是可以将其看做两个盘 1最下面的盘 2其他的盘
- 先把最上面的盘 A->B
- 最下面的盘 A->C
- 把C的所有盘 B->C
class Algorithm{
public static void main(String[] args) {
int num = 5;
hannuoTower(num,'A','B','C');
}
/**
*
* @param num 盘数
* @param a 柱
* @param b 柱
* @param c 柱
*/
public static void hannuoTower(int num,char a,char b,char c) {
//只有一个盘
if (num == 1){
System.out.println("第1个盘移动:" + a + "->" + c);
}else {
//n >= 2的情况 总是可以看作两个盘 1底盘 2其他盘
//1.将其他盘移动 a-b
hannuoTower(num-1,a,c,b);
//2.底盘移动 a-c
System.out.println("第" + num + "个盘移动:" + a + "->" + c);
//3.把b柱的盘移动 b-c
hannuoTower(num-1,b,a,c);
}
}
}
3.动态规划
背包问题
背包问题:有一个背包,容量为4磅 , 现有如下物品

- 要求达到的目标为装入的背包的总价值最大,并且重量不超出
- 要求装入的物品不能重复
- 这里属于01背包,即每个物品最多放一个。而无限背包可以转化为01背包。
- 主要思想,利用动态规划来解决。每次遍历到的第i个物品,根据w[i]和v[i]来确定是否需要将该物品放入背包中。即对于给定的n个物品,设v[i]、w[i]分别为第i个物品的价值和重量,C为背包的容量。再令v[i][j]表示在前i个物品中能够装入容量为j的背包中的最大价值。则我们有下面的结果:
(1) v[i][0]=v[0][j]=0; //表示 填入表 第一行和第一列是0
(2) 当w[i]> j 时:v[i][j]=v[i-1][j] // 当准备加入新增的商品的容量大于 当前背包的容量时,就直接使用上一个单元格的装入策略
(3) 当j>=w[i]时: v[i][j]=max{v[i-1][j], v[i]+v[i-1][j-w[i]]}
// 当 准备加入的新增的商品的容量小于等于当前背包的容量,
// 装入的方式:
v[i-1][j]: 就是上一个单元格的装入的最大值
v[i] : 表示当前商品的价值
v[i-1][j-w[i]] : 装入i-1商品,到剩余空间j-w[i]的最大值
当j>=w[i]时: v[i][j]=max{v[i-1][j], v[i]+v[i-1][j-w[i]]}
class Algorithm{
public static void main(String[] args) {
int[] w = {1,4,3};//物品重量
int[] val = {1500,3000,2000};//物品价值
int m = 4;//背包容量
int n = w.length;//物品的个数i
//v[i][j]表示在前i个物品中能够装入剩余容量为j的背包中的最大价值
int[][] v = new int[n+1][m+1];
//记录商品
int[][] path = new int[n+1][m+1];
//初始化第一行和第一列
for (int i = 0; i < v.length; i++) {
v[i][0] = 0;
}
for (int i = 0; i < v[0].length; i++) {
v[0][i] = 0;
}
//动态规划处理
for (int i = 1; i < v.length; i++) {//不处理第一行
for (int j = 1; j < v[0].length; j++) {//不处理第一列
if (w[i-1] > j){
v[i][j] = v[i-1][j];
}else {
//v[i][j] = Math.max(v[i-1][j],val[i-1] + v[i-1][j-w[i-1]]);
if (v[i-1][j] < (val[i-1] + v[i-1][j-w[i-1]])){
v[i][j] = val[i-1] + v[i-1][j-w[i-1]];
path[i][j] = 1;
}else {
v[i][j] = v[i-1][j];
}
}
}
}
for (int i = 0; i < v.length; i++) {
for (int j = 0; j < v[0].length; j++) {
System.out.print(v[i][j]+" ");
}
System.out.println();
}
System.out.println("=================");
int i = path.length - 1;
int j = path[0].length -1;
while (i > 0 && j > 0){
if (path[i][j] == 1){
System.out.println("放入第"+i+"个物品");
j -= w[i-1];
}
i--;
}
}
}
3.KMP
- 它是⽤来解决字符串查找的问题,可以在⼀个字符串(S)中查找⼀个⼦串(W)出现的位置。KMP 算法把字符匹配的时间复杂度缩⼩到O(m+n) ,⽽空间复杂度也只有O(m)。
1.利用最长公共前后缀 添加-1后 移位 建前缀表 Prefix Table
2.未匹配到的字母 P表对应值的为i 移动子串将对应下标为i的字符移位到匹配为不成功处,并从此处继续扫描
public class test {
public static void main(String[] args) {
String a = "ababababcabaab";
String b = "aba";
int n = a.length();
int m = b.length();
char [] s = new char[n];
char [] w = new char[m];
for (int i = 0; i < n; i++) {
s[i] = a.charAt(i);
}
for (int i = 0; i < m; i++) {
w[i] = b.charAt(i);
}
int [] prefix = initial(w,m);
int [] next = move(m,prefix);
System.out.println("满足匹配!共匹配"+match(m,n,next,s,w)+"次");
}
//创建前缀表
public static int[] initial(char[] s,int m){
int [] prefix = new int [m];
prefix[0] = 0;
int len = 0;
int i = 1;
//建立方式 a->ab->aba 若下一个字母与下标len字符匹配 则最长公共前后缀len++
while(i<m){
if(s[i] == s[len]){
len++;
prefix[i] = len;
i++;
}else{
//若不匹配 斜着往前一位 判断是否与前一位匹配 循环
if(len>0){
len = prefix[len-1];
}else{
prefix[i] = len;
i++;
}
}
}
return prefix;
}
//移位表
public static int[] move(int m,int [] prefix){
for (int i = m-1; i > 0; i--) {
prefix [i] = prefix[i-1];
}
prefix [0] = -1;
return prefix;
}
public static int match(int m,int n,int[]next,char[]s,char[]w){
int j=0,i=0,result=0;
//这里要注意下标越界
while(j<m && (n-i)>m){
if(s[i] == w[j]){
i++;
j++;
}else{
j = next[j];
if(next[j] == -1){
i++;
j++;
}
}
if(j==m-1 && s[i]==w[j]){
System.out.println("匹配成功!下标为"+(i-j));
result++;
j = next[j];
}
}
return result;
}
}
4.贪心算法
- 贪婪算法(贪心算法)是指在对问题进行求解时,在每一步选择中都采取最好或者最优(即最有利)的选择,从而希望能够导致结果是最好或者最优的算法
- 贪婪算法所得到的结果不一定是最优的结果(有时候会是最优解),但是都是相对近似(接近)最优解的结果
集合覆盖
- 假设存在如下表的需要付费的广播台,以及广播台信号可以覆盖的地区。 如何选择最少的广播台,让所有的地区都可以接收到信号

- 思路分析:
目前并没有算法可以快速计算得到准备的值, 使用贪婪算法,则可以得到非常接近的解,并且效率高。选择策略上,因为需要覆盖全部地区的最小集合:
- 遍历所有的广播电台, 找到一个覆盖最多地区的电台
- 将这个电台加入到一个集合中(比如ArrayList), 想办法把该电台覆盖的地区在下次从总地区比较时去掉。
- 重复第1步直到覆盖了全部的地区
import java.util.*;
class Algorithm{
public static void main(String[] args) {
String[] k1 = {"北京", "上海", "天津"};
String[] k2 = {"广州", "北京", "深圳"};
String[] k3 = {"成都", "上海", "杭州"};
String[] k4 = {"上海", "天津"};
String[] k5 = {"杭州", "大连"};
//电台存储为HashMap结构 所有待覆盖地区存储为HashSet结构
HashSet<String> allAreas = new HashSet();
HashMap<String,HashSet> broadcasts = add(k1,k2,k3,k4,k5);
for (String key:broadcasts.keySet()) {
System.out.println(key + "\t" +broadcasts.get(key));
allAreas.addAll(broadcasts.get(key));
}
for (String s:allAreas) {
System.out.print(s+"\t");
}
System.out.println();
//选择电台存储为ArrayList
ArrayList<String> selects = new ArrayList();
//临时集合 存放电台覆盖地区与总地区的交集
HashSet<String> tempSet = new HashSet<>();
//定义maxKey 覆盖最多未覆盖地区的电台啊
String maxKey = "";
while (allAreas.size() != 0){
//每次将maxKey置空
maxKey = null;
//遍历电台 取出对应的key
for (String key : broadcasts.keySet()) {
//清空上一次的temSet
tempSet.clear();
//当前key能覆盖的地区
HashSet<String> areas = broadcasts.get(key);
tempSet.addAll(areas);
//找出与未覆盖地区重合的地区
tempSet.retainAll(allAreas);
//如果当前key的未覆盖地区数 maxKey比更多 则重新给maxKey赋值
//这里体现贪心算法 每次都选择最优
if (tempSet.size() > 0 && (maxKey == null ||
tempSet.size() > broadcasts.get(maxKey).size())){
maxKey = key;
}
}
if (maxKey != null){
selects.add(maxKey);
//将maxKey覆盖的地区 从allAreas清除掉
allAreas.removeAll(broadcasts.get(maxKey));
}
}
System.out.println("选择结果:"+selects);
}
public static HashMap add(String[] k1, String[] k2, String[] k3, String[] k4, String[] k5){
HashMap<String,HashSet> map = new HashMap();
HashSet<String> set1 = new HashSet();
HashSet<String> set2 = new HashSet();
HashSet<String> set3 = new HashSet();
HashSet<String> set4 = new HashSet();
HashSet<String> set5 = new HashSet();
for (String s:k1 ) {
set1.add(s);
}
map.put("k1",set1);
for (String s:k2 ) {
set2.add(s);
}
map.put("k2",set2);
for (String s:k3 ) {
set3.add(s);
}
map.put("k3",set3);
for (String s:k4 ) {
set4.add(s);
}
map.put("k4",set4);
for (String s:k5 ) {
set5.add(s);
}
map.put("k5",set5);
return map;
}
}
5.普里姆算法(Prim)
- 普利姆(Prim)算法求最小生成树,也就是在包含n个顶点的连通图中,找出只有(n-1)条边包含所有n个顶点的连通子图,也就是所谓的极小连通子图
- 普利姆的算法如下:
- 设G=(V,E)是连通网,T=(U,D)是最小生成树,V,U是顶点集合,E,D是边的集合
- 若从顶点u开始构造最小生成树,则从集合V中取出顶点u放入集合U中,标记顶点v的visited[u]=1
- 若集合U中顶点ui与集合V-U中的顶点vj之间存在边,则寻找这些边中权值最小的边,但不能构成回路,将顶点vj加入集合U中,将边(ui,vj)加入集合D中,标记visited[vj]=1
- 重复步骤②,直到U与V相等,即所有顶点都被标记为访问过,此时D中有n-1条边
修路问题
- 修路问题本质就是就是最小生成树问题, 先介绍一下最小生成树(Minimum Cost Spanning Tree),简称MST。
- 给定一个带权的无向连通图,如何选取一棵生成树,使树上所有边上权的总和为最小,这叫最小生成树
- N个顶点,一定有N-1条边
- 包含全部顶点
- N-1条边都在图中
求最小生成树的算法主要是普里姆算法和克鲁斯卡尔算法


import java.util.Arrays;
class Algorithm{
public static void main(String[] args) {
char[] data = {'A','B','C','D','E','F','G'};
int verxs = data.length;
//10000表示不连通
int[][] weight = { {10000,5,7,10000,10000,10000,2},
{5,10000,10000,9,10000,10000,3},
{7,10000,10000,10000,8,10000,10000},
{10000,9,10000,10000,10000,4,10000},
{10000,10000,8,10000,10000,5,4},
{10000,10000,10000,4,5,10000,6},
{2,3,10000,10000,4,6,10000} };
Graph graph = new Graph(verxs);
MinTree tree = new MinTree();
tree.create(graph,verxs,data,weight);
tree.show(graph);
tree.prim(graph,1);
}
}
//创建最小生成树
class MinTree{
//创建图的邻接矩阵
/**
*
* @param graph 图对象
* @param verxs 对应顶点个数
* @param data 顶点值
* @param weight 图的邻接矩阵
*/
public void create(Graph graph,int verxs,char[] data,int[][] weight){
for (int i = 0; i < verxs; i++) {
graph.data[i] = data[i];
for (int j = 0; j < verxs; j++) {
graph.weight[i][j] = weight[i][j];
}
}
}
public void show(Graph graph){
for (int[] i:graph.weight) {
System.out.println(Arrays.toString(i));
}
}
//prim算法
/**
*
* @param graph 图对象
* @param index 从哪个节点开始生成
*/
public void prim(Graph graph,int index){
//标记节点是否被访问过
int[] visited = new int[graph.verxs];
visited[index] = 1;
//h1 h2记录两个顶点的下标
int h1 = -1;
int h2 = -1;
int minWeight = 10000;
for (int k = 1; k < graph.verxs; k++) {//算法结束 会有grapg.verxs-1条边
//确定每次生成的子图 是权重最小的
for (int i = 0; i < graph.verxs; i++) {//当前访问
for (int j = 0; j < graph.verxs; j++) {//未访问
if (visited[i]==1 && visited[j]==0 && (graph.weight[i][j] < minWeight)) {
minWeight = graph.weight[i][j];
h1 = i;
h2 = j;
}
}
}
//找到了一条权值最小的边
System.out.println("边:" + graph.data[h1] + "-->" + graph.data[h2] + "\t" +minWeight);
visited[h2] = 1;
minWeight = 10000;
}
}
}
class Graph{
int verxs;//图的节点个数
char[] data;//存放节点数据
int[][] weight;//存放边 即邻接矩阵
public Graph(int verxs) {
this.verxs = verxs;
data = new char[verxs];
weight = new int[verxs][verxs];
}
}
5.克鲁斯卡尔(Kruskal)算法
- 克鲁斯卡尔(Kruskal)算法,是用来求加权连通图的最小生成树的算法。
- 基本思想:按照权值从小到大的顺序选择n-1条边,并保证这n-1条边不构成回路
- 具体做法:首先构造一个只含n个顶点的森林,然后依权值从小到大从连通网中选择边加入到森林中,并使森林中不产生回路,直至森林变成一棵树为止
- 将边添加到最小生成树中时,怎么样判断是否形成了回路?
- 处理方式是:记录顶点在"最小生成树"中的终点,顶点的终点是"在最小生成树中与它连通的最大顶点"(ASII)。 然后每次需要将一条边添加到最小生存树时,判断该边的两个顶点的终点是否重合,重合的话则会构成回路。
公交站问题

- 有北京有新增7个站点(A, B, C, D, E, F, G) ,现在需要修路把7个站点连通
各个站点的距离用边线表示(权) ,比如 A – B 距离 12公里
问:如何修路保证各个站点都能连通,并且总的修建公路总里程最短?
import com.sun.javafx.geom.Edge;
import com.sun.xml.internal.ws.api.model.wsdl.WSDLOutput;
import java.util.Arrays;
class Kruskal{
private int edgeNum;//边的个数
private char[] vertexs;//顶点数组
private int[][] matrix;//邻接矩阵
private static final int INF = Integer.MAX_VALUE;//顶点不能连通
public static void main(String[] args) {
char[] vertexs = {'A', 'B', 'C', 'D', 'E', 'F', 'G'};
int matrix[][] = { /*A*//*B*//*C*//*D*//*E*//*F*//*G*/
/*A*/ { 0, 12, INF, INF, INF, 16, 14},
/*B*/ { 12, 0, 10, INF, INF, 7, INF},
/*C*/ { INF, 10, 0, 3, 5, 6, INF},
/*D*/ { INF, INF, 3, 0, 4, INF, INF},
/*E*/ { INF, INF, 5, 4, 0, 2, 8},
/*F*/ { 16, 7, 6, INF, 2, 0, 9},
/*G*/ { 14, INF, INF, INF, 8, 9, 0}};
Kruskal kruskal = new Kruskal(vertexs,matrix);
kruskal.print();
kruskal.kruskal();
}
//初始化顶点数和边数
public Kruskal(char[] vertexs, int[][] matrix) {
//初始化顶点数和边的个数
int vlen = vertexs.length;
//初始化顶点, 复制拷贝的方式
this.vertexs = new char[vlen];
for(int i = 0; i < vertexs.length; i++) {
this.vertexs[i] = vertexs[i];
}
//初始化边, 使用的是复制拷贝的方式
this.matrix = new int[vlen][vlen];
for(int i = 0; i < vlen; i++) {
for(int j= 0; j < vlen; j++) {
this.matrix[i][j] = matrix[i][j];
}
}
//统计边
for(int i =0; i < vlen; i++) {
for(int j = i+1; j < vlen; j++) {
if(this.matrix[i][j] != INF) {
edgeNum++;
}
}
}
}
//
public void kruskal(){
int index = 0;//表示最后结果数组的索引
int[] ends = new int[edgeNum];//保存已有最小树 中每个顶点的终点
//创建结果数组
Edges[] rets = new Edges[edgeNum];
//获取图中 所有边的集合
Edges[] edges = getEdges();
System.out.println("图的边集合" + "\n" + Arrays.toString(edges));
//排序
sort(edges);
//判断回路
for (int i = 0; i < edgeNum; i++) {
//获取第i边的第一个顶点
int p1 = getPosition(edges[i].start);
//第二个顶点
int p2 = getPosition(edges[i].end);
//获取p1 p2的终点
int m = getEnd(ends,p1);
int n = getEnd(ends,p2);
//构成回路
if ( m!=n){
ends[m] = n;//设置m的终点
rets[index++] = edges[i];
}
}
//统计打印最小生成树
for (int i = 0; i < index; i++) {
System.out.println(rets[i]);
}
}
//遍历临接矩阵
public void print(){
System.out.println("邻接矩阵");
for (int[] i:matrix) {
System.out.println(Arrays.toString(i));
}
}
//冒泡对边排序
public void sort(Edges[] edges){
for (int i = 0; i < edges.length -1; i++) {
for (int j = 0; j < edges.length -1-i; j++) {
if (edges[j].weight > edges[j+1].weight){
Edges tem = edges[j];
edges[j] = edges[j+1];
edges[j+1] = tem;
}
}
}
}
//得到顶点对应下标
public int getPosition(char ch){
for (int i = 0; i < vertexs.length; i++) {
if (vertexs[i] == ch){
return i;
}
}
return -1;
}
//获取图中的边 放到Edges[] 后面需要遍历该数组
public Edges[] getEdges(){
int index = 0;
Edges[] edges= new Edges[edgeNum];
for (int i = 0; i < vertexs.length; i++) {
for (int j = i+1; j < vertexs.length; j++) {
if (matrix[i][j] != INF){
edges[index++] = new Edges(vertexs[i],vertexs[j],matrix[i][j]);
}
}
}
return edges;
}
//获取下标为i的顶点的终点 返回对应终点下标
public int getEnd(int[] ends,int i){
while (ends[i] != 0){
i = ends[i];
}
return i;
}
}
//表示一条边
class Edges{
char start;//起点
char end;//另外一个点
int weight;
public Edges(char start, char end, int weight) {
this.start = start;
this.end = end;
this.weight = weight;
}
@Override
public String toString() {
return "EData{<" + start +
"," + end + ">" +
"," + weight +
'}';
}
}
6.迪杰斯特拉算法
最短路径问题

- 战争时期,胜利乡有7个村庄(A, B, C, D, E, F, G) ,现在有六个邮差,从G点出发,需要分别把邮件分别送到 A, B, C , D, E, F 六个村庄,各个村庄的距离用边线表示(权) ,比如 A – B 距离 5公里
- 问:如何计算出G村庄到 其它各个村庄的最短距离?
设置出发顶点为v,顶点集合V{v1,v2,vi…},v到V中各顶点的距离构成距离集合Dis,Dis{d1,d2,di…},Dis集合记录着v到图中各顶点的距离(到自身可以看作0,v到vi距离对应为di)
- 从Dis中选择值最小的di并移出Dis集合,同时移出V集合中对应的顶点vi,此时的v到vi即为最短路径
- 更新Dis集合,更新规则为:比较v到V集合中顶点的距离值,与v通过vi到V集合中顶点的距离值,保留值较小的一个(同时也应该更新顶点的前驱节点为vi,表明是通过vi到达的)
- 重复执行两步骤,直到最短路径顶点为目标顶点即可结束
import java.util.Arrays;
class Dijkstra{
public static void main(String[] args) {
char[] vertex = { 'A', 'B', 'C', 'D', 'E', 'F', 'G' };
int[][] matrix = new int[vertex.length][vertex.length];
final int N = 65535;// 表示不可以连接
matrix[0]=new int[]{N,5,7,N,N,N,2};
matrix[1]=new int[]{5,N,N,9,N,N,3};
matrix[2]=new int[]{7,N,N,N,8,N,N};
matrix[3]=new int[]{N,9,N,N,N,4,N};
matrix[4]=new int[]{N,N,8,N,N,5,4};
matrix[5]=new int[]{N,N,N,4,5,N,6};
matrix[6]=new int[]{2,3,N,N,4,6,N};
//创建 Graph 对象
Graph graph = new Graph(vertex, matrix);
//测试, 看看图的邻接矩阵是否 ok
graph.show();
//测试迪杰斯特拉算法
graph.dsj(6);
graph.showDSJ();
}
}
class Graph{
private char[] vertex;//顶点数组
private int[][] matrix;//临接矩阵
private VisitedVertex vv;//已经访问的顶点集合
//构造器
public Graph(char[] vertex, int[][] matrix) {
this.vertex = vertex;
this.matrix = matrix;
}
public void show(){
for (int[] link:matrix) {
System.out.println(Arrays.toString(link));
}
}
/**
*
* @param index 出发节点的下标
*/
public void dsj(int index){
vv = new VisitedVertex(vertex.length, index);
update(index);
for (int i = 1; i < vertex.length; i++) {
index = vv.updateArr();
update(index);
}
}
//更新index下标顶点到周围顶点的距离 和周围顶点的前驱节点
private void update(int index){
int len = 0;
//遍历矩阵的matrix[index]行
for (int i = 0; i < matrix[index].length; i++) {
//len = 出发点到index的距离 + index到j顶点的距离
len = vv.getDis(index) + matrix[index][i];
if (!vv.in(i) && len < vv.getDis(i)){
vv.updatePre(i,index);
vv.updateDis(i,len);
}
}
}
public void showDSJ(){
vv.show();
}
}
class VisitedVertex {
// 记录各个顶点是否访问过 1 表示访问过,0 未访问,会动态更新
public int[] already_arr;
// 每个下标对应的值为前一个顶点下标, 会动态更新
public int[] pre_visited;
// 记录出发顶点到其他所有顶点的距离,比如 G 为出发顶点,就会记录 G 到其它顶点的距离,会动态更新,求的最短距离就会存放dis
public int[] dis;
//构造器
/**
*
* @param length 顶点个数
* @param index 出发顶点下标
*/
public VisitedVertex(int length,int index){
this.already_arr = new int[length];
this.pre_visited = new int[length];
this.dis = new int[length];
//初始化dis
Arrays.fill(dis,65535);
this.already_arr[index] = 1;
this.dis[index] = 0;
}
/**
* 判断index顶点是否被访问过
* @param index 定点下标
* @return true已访问
*/
public boolean in (int index) {
return already_arr[index] == 1;
}
/**
* 更新出发顶点到index顶点的距离
* @param index
* @param len
*/
public void updateDis(int index,int len){
dis[index] = len;
}
/**
* 更新pre顶点的前驱顶点为index
* @param pre
* @param index
*/
public void updatePre(int pre,int index){
pre_visited[pre] = index;
}
//返回出发点到Index的距离
public int getDis(int index){
return dis[index];
}
//继续访问并选择新的开始访问节点
public int updateArr(){
int min = 65535;
int index = 0;
for (int i = 0; i < already_arr.length; i++) {
if (already_arr[i] == 0 && dis[i]<min){
min = dis[i];
index = i;
}
}
already_arr[index] = 1;
return index;
}
public void show(){
System.out.println("==========================");
for (int i:already_arr) {
System.out.print(i+"\t");
}
System.out.println();
for (int i:pre_visited) {
System.out.print(i+"\t");
}
System.out.println();
for (int i:dis) {
System.out.print(i+"\t");
}
}
}
7.弗洛伊德(Floyd)算法
- 弗洛伊德算法 VS 迪杰斯特拉算法:迪杰斯特拉算法通过选定的被访问顶点,求出从出发访问顶点到其他顶点的最短路径;弗洛伊德算法中每一个顶点都是出发访问点,所以需要将每一个顶点看做被访问顶点,求出从每一个顶点到其他顶点的最短路径。
- 设置顶点vi到顶点vk的最短路径已知为Lik,顶点vk到vj的最短路径已知为Lkj,顶点vi到vj的路径为Lij,则vi到vj的最短路径为:min((Lik+Lkj),Lij),vk的取值为图中所有顶点,则可获得vi到vj的最短路径
import java.util.Arrays;
class Floyd{
public static void main(String[] args) {
// 测试看看图是否创建成功
char[] vertex = { 'A', 'B', 'C', 'D', 'E', 'F', 'G' };
//创建邻接矩阵
int[][] matrix = new int[vertex.length][vertex.length];
final int N = 65535;
matrix[0] = new int[] { 0, 5, 7, N, N, N, 2 };
matrix[1] = new int[] { 5, 0, N, 9, N, N, 3 };
matrix[2] = new int[] { 7, N, 0, N, 8, N, N };
matrix[3] = new int[] { N, 9, N, 0, N, 4, N };
matrix[4] = new int[] { N, N, 8, N, 0, 5, 4 };
matrix[5] = new int[] { N, N, N, 4, 5, 0, 6 };
matrix[6] = new int[] { 2, 3, N, N, 4, 6, 0 };
//创建 Graph 对象
Graph graph = new Graph(vertex.length, matrix, vertex);
graph.show();
graph.floyd();
graph.show();
}
}
class Graph{
private char[] vertex;//存放顶点
private int[][] dis;//从各个顶点出发到其他节点的距离
private int[][] pre;//对应距离表dis 记录到达目标节点的前驱节点
/**
*
* @param length 顶点个数
* @param matrix 临接矩阵
* @param vertex 顶点数组
*/
public Graph(int length,int[][] matrix,char[] vertex){
this.vertex = vertex;
this.dis = matrix;
this.pre = new int[length][length];
//对pre初始化
for (int i = 0; i < length; i++) {
Arrays.fill(pre[i],i);
}
}
//显示pre dis 数组
public void show(){
System.out.println("距离表dis:");
for (int[] i:dis) {
System.out.println(Arrays.toString(i));
}
System.out.println("前驱表pre:");
for (int i = 0; i < vertex.length; i++) {
for (int j = 0; j < vertex.length; j++) {
System.out.print(vertex[pre[i][j]]+"\t");
}
System.out.println();
}
}
public void floyd(){
int len = 0;//保存距离
for (int k = 0; k < dis.length; k++) {//先遍历中间顶点
for (int i = 0; i < dis.length; i++) {//出发顶点
for (int j = 0; j < dis.length; j++) {//到达顶点
len = dis[i][k] + dis[k][j];
if (len < dis[i][j]){//更新最短距离
dis[i][j] = len;
pre[i][j] = pre[k][j];
pre[j][i] = pre[k][j];
}
}
}
}
}
}

3339

被折叠的 条评论
为什么被折叠?



