经典的说法:程序=算法+数据结构,因此,算法与数据结构在程序设计中至关重要,我们务必熟练掌握并正确运用它门。
一、常用算法解析
1、算法定义与作用
(1)算法定义
算法由英文Algorithm 直译而来, 即“运算法则”。广义上讲, 就是指“达成某种目的的步骤”。
日常生活中,“早上起床—换衣服—吃早饭—骑自行车上学” 就是一种算法。
在计算机界,我们将通过数据处理、数值运算、组合计算、模拟等操作解决问题的步骤称为算法。
(2)算法作用与特点
有穷性:是指算法必须能在执行有限个步骤之后终止。
确切性:算法的每一步骤必须有确切的定义。
输入项:一个算法有0个或多个输入,以刻画运算对象的初始情况,所谓0个输入是指算法本身定出了初始条件。
输出项:一个算法有一个或多个输出,以反映对输入数据加工后的结果。没有输出的算法是毫无意义的。
可行性:算法中执行的任何计算步骤都是可以被分解为基本的可执行的操作步骤,即每个计算步骤都可以在有限时间内完成。
2、常用算法
2.1 排序算法,有五大类8种
(1)交换类排序
冒泡排序:通过不断交换相邻元素将最大(或最小)元素逐步交换到数列的最后。
快速排序:通过选择一个基准元素,将数列分割成小于基准和大于基准的两部分,递归地对两部分进行排序。
(2)插入类排序:
直接插入排序:将待排序的元素一个个地插入到已排序部分的合适位置,类似于整理牌的过程。
希尔排序:也叫缩小增量排序,它通过比较相距一定间隔的元素来进行,各趟比较所用的距离随着算法的进行而减小,直到只比较相邻元素的最后一趟排序为止。
(3)选择类排序:
简单选择排序:每次从未排序的部分选择最小(或最大)的元素,并放置到已排序部分的末尾。
堆排序:通过将待排序数列构建成最大(或最小)堆,不断将堆顶元素与末尾元素交换并调整,得到有序序列。
(4)归并排序:采用分治的思想,将待排序数列分成两个子数列,递归地进行排序,然后合并两个有序子数列。
(5)基数排序:根据元素的位数,按个位、十位、百位等依次进行排序,适用于整数的排序。
案例解析:
/**1.冒泡排序**/
public static void bubbleSort(int[] arr) {
for (int i = 0; i < arr.length; i++) {// 控制比较次数
for (int j = 0; j < arr.length - i - 1; j++) {
if (arr[j] < arr[j + 1]) {
// 临时用于交换的变量
int temp = arr[j];
// 交换前后的元素
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
// 遍历数组
traversalArr(arr);
}
/**2.1 直接插入排序**/
public static void insertSort(int[] arr) {
for (int i = 0; i < arr.length; i++) {
// 从小到大排序
for (int j = i; j > 0; j--) {
if (arr[j] < arr[j - 1]) {
int temp = arr[j];
arr[j] = arr[j - 1];
arr[j - 1] = temp;
}
} // for1
} // for2
traversalArr(arr);
}
/**2.2希尔排序**/
public static void shellSort(int[] arr) {
int n = arr.length;
for (int gap = n / 2; gap > 0; gap /= 2) {
for (int i = gap; i < n; i++) {
int temp = arr[i];
int j;
for (j = i; j >= gap && arr[j - gap] > temp; j -= gap) {
// 交换
arr[j] = arr[j - gap];
}
// 交换
arr[j] = temp;
}
}
// 遍历数组
traversalArr(arr);
}
/**3.简单选择排序**/
public static void selectSort(int[] arr) {
for (int i = 0; i < arr.length; i++) {
for (int j = i; j < arr.length; j++) {
int temp = arr[i];
if (arr[i] > arr[j]) {
arr[i] = arr[j];
arr[j] = temp;
}
} // for1
} // for2
// 遍历数组
traversalArr(arr);
}
/**4.归并排序**/
//合并数组
public static void mergeSort(int[] arr) {
if (arr == null || arr.length <= 1) {
return;
}
int[] temp = new int[arr.length];
//合并数组
mergeSort(arr, temp, 0, arr.length - 1);
// 遍历数组
traversalArr(arr);
}
// 合并数组1
private static void mergeSort(int[] arr, int[] temp, int left, int right) {
if (left < right) {
int mid = left + (right - left) / 2;
mergeSort(arr, temp, left, mid);
mergeSort(arr, temp, mid + 1, right);
merge(arr, temp, left, mid, right);
}
}
// 合并数组2
private static void merge(int[] arr, int[] temp, int left, int mid, int right) {
for (int i = left; i <= right; i++) {
temp[i] = arr[i];
}
int i = left;
int j = mid + 1;
int k = left;
while (i <= mid && j <= right) {
if (temp[i] <= temp[j]) {
arr[k++] = temp[i++];
} else {
arr[k++] = temp[j++];
}
}
while (i <= mid) {
arr[k++] = temp[i++];
}
}
/**遍历数组**/
public static void traversalArr(int[] arr) {
for (int temp : arr) {
System.out.print(temp + " ");
} // for
System.out.println();
}// traversal
问:希尔排序与归并排序有何区别
2.2 二分查找算法
查找有顺序查找,二分查找等方式。
顺序查找:就是从第一个元素开始,按索引顺序遍历待查找序列,直到找出给定目标或者查找失败,需要遍历整个待查序列,效率低。
二分查找:指在已排序的线性表中折半查找(Binary Search)元素的方法,它是一种效率较高的查找方法。
二分查找算法案例:
/**二分查找解析**/
public static void binaryFind(int n, int[] arr) {
int f = -1;
// 开始下标
int start = 0;
// 结束下标
int end = arr.length - 1;
while (start <= end) {
int middle = (end + start) / 2;// 防溢出
if (n == arr[middle]) {
f = middle;
break;
} else if (n < arr[middle]) {
end = middle - 1;
} else {
start = middle + 1;
}
} // while
System.out.println(n + "找到的:" + arr[f]);
}// binary
2.3 基础算法
(1)动态规划法
每次决策依赖于当前状态,又随即引起状态的转移。一个决策序列就是在变化的状态中产生出来的,所以,这种多阶段最优化决策解决问题的过程就称为动态规划。递归是动态归划法的一种应用。
//1,斐波那锲数列实现,从第二项开始,下一项等于前两项之和,非递归方式
public int feibonaqie(int n) {
if(n==0) {
return 1;
}
else if(n==1) {
return 2;
}
else {
int[] result=new int[n];//用来存储每一次的状态
result[0]=1;
result[1]=2;
for(int i=2;i<n;i++) {
result[i]=result[i-1]+result[i-2];
}
//返回值
return result[n];
}
}
//2.给出N个数字,不改变它们的相对位置,在中间加入K个乘号和N-K-1个加号,(括号随便加)使最终结果尽量大。因为乘号和加号一共就是N-1个了,所以恰好每两个相
//邻数字之间都有一个符号。例如:
//N=5,K=2,5个数字分别为1、2、3、4、5,可以为:
//12(3+4+5)=24
//1*(2+3)(4+5)=45
//解析:dp[4][1]的含义就是前4位数(0,1,2,3)中间有一称号的最大值,此处为9,是经过这样一个循环得到的
//(12+3)=5
//(1+2)*3=9
public class MyMain {
public static void main(String[] args) {
Scanner cin = new Scanner(System.in);
int n, k;
n = cin.nextInt();
k = cin.nextInt();
long[][] dp = new long[n+1][n+1];
long[] sum = new long[n+1];
long t;
//初始化dp数组
for(int i = 0; i <= n; i++) {
for(int j = 0; j <= k; j++) {
dp[i][j] = 0;
}
}
//得到累加和
sum[0] = 0;
for(int i = 1; i <= n; i++) {
t = cin.nextLong();
sum[i] = sum[i-1] + t;
dp[i][0] = sum[i];
}
//计算
for(int i = 2; i <= n; i++) { //数的个数
for(int j = 1; j <= k; j++) { //乘号的个数
for(int tt = 1; tt < i; tt++) { //遍历最后一个乘号的位置
dp[i][j] = Math.max(dp[i][j], dp[tt][j-1] * (sum[i] - sum[tt]));
}
}
}
//输出
System.out.println(dp[n][k]);
cin.close();
}
}
(2)分治法
就是把一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题,直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并。这个技巧是很多高效算法的基础,如排序算法(快速排序,归并排序),二分搜索,傅立叶变换等。
//问题解析:汉诺塔(又称河内塔)问题是源于印度一个古老传说的益智玩具。大梵天创造世界的时候做了三根金刚石柱子,在一根柱子上从下往上按照大小顺序摞着64片黄金
//圆盘。大梵天命令婆罗门把圆盘从下面开始按大小顺序重新摆放在另一根柱子上。并且规定,在小圆盘上不能放大圆盘,在三根柱子之间一次只能移动一个圆盘。
public class HanoiTower{
public static void main(String[] args) {
//测试
hanoiTower(5, 'A', 'B', 'C');
}
//汉诺实现
public static void hanoiTower(int num, char a, char b, char c) {
// 如果当前只有一个盘,直接从A->C
if(num == 1) {
System.out.println("第" + num + "个盘子从 " + a + "->" + c);
} else { // 有两个及以上的盘子
// 先把除了最下面那个盘子外的所有盘子从A->B
hanoiTower(num - 1, a, c, b);
// 把最下面这个盘子从 A-C
System.out.println("第" + num + "个盘子从" + a + "->" + c);
// 在把B上面的盘子移动到C
hanoiTower(num - 1, b, a, c);
}
}
}
(3)贪心法
在对问题求解时,总是做出在当前看来是最好的选择,也就是说,不从整体最优上加以考虑,我们所做出的仅是在某种意义上的局部最优解。
//问题解析,找零钱
//假设有25、10、5、1四种面额的硬币,要找给顾客99美分的零钱。使用贪心算法可以简单地选择最大面额的硬币进行找零。
public void giveLittle(){
int[] coins = {25,10,5,1};
int amout = 99;
int count = 0;
for(int coin : coins){
while(amout >= coin){
amout -= coin;
count++;
}
}
//输出
System.out.println("南非要找零的硬币数量:"+count);
}
二、常用数据结构解析
2.1 数据结构定义与作用
数据结构是计算机存储、组织数据的方式,是数据的组织形式和它们之间的关系,在计算机程序中非常重要。
数据结构主要研究的是如何在计算机中组织和存储数据,以便有效地访问和修改这些数据。
数据结构一般有:线性结构与非线性结构两种方式。
线性结构:数组、栈、链表、队列
非线性结构:二叉树、图
2.2 常用数据结构
1. 线性方式
(1)数组是一种线性结构,它可以存储一系列具有相同类型的元素。数组的特点是它的元素排列是有序的,且在内存中连续存储,每个元素占据相同大小的内存空间。它查询快,增删慢。
比如:int[] ages=new int[8];
数组的操作有:元素的增删改查,元素的排序等。
(2)栈stack,后进先出LIFO
最早进入的元素存放的位置叫作栈底(bottom),最后进入的元素存放的位置叫作栈顶(top)。
(3)队列Queue,一端插入,另一端删除,先进先出FIFO
队列的出口端叫作队头(front),队列的入口端叫作队尾(rear)。
(4)链表linked list
链表分为:单向链表,双向链表(包括双向循环链表)
在Java集合中有数组+键表的应用
2. 非线性方式
(1)二叉树Binary Tree,是每个结点最多有两个子树的树结构。通常子树被称作“左子树”和“右子树”。
mysql中有红黑树、B+树的应用
(2)图Graph,比线性表与树更复杂的数据结构,研究数据元系之间多对多关系
图是一种复杂数据结构,其中结点可以具有零个或多个相邻元素。两个结点之间的连接称为边,结点也可以称为顶点。
概念为:由顶点(Vertex)集和边( Edge)集组成, 记为G=(V,E),其中V 是有穷非空集合,称为顶点集,v ∈V称为顶点。E是有穷集合,称为边集, e∈E 称为边。
几种常见的图:
-
在图中,如果代表边的顶点对(或序偶)是无序的,则称为无向图。无向图中代表边的无序顶点对通常用圆括号括起来,用以表示一条无向边。如(i,j)表示顶点i与顶点j的一条无向边,显然,(i,j)和(j,i)所代表的是同一条边。
-
如果表示边的顶点对(或序偶)是有序的,则称为有向图。在有向图中代表边的顶点对通常用尖括号括起来,用以表示一条有向边(又称为弧),如<i,j>表示从顶点i到顶点j的一条边。
-
图中每一条边都可以附有一个对应的数值,这种与边相关的数值称为权。权可以表示从一个顶点到另一个顶点的距离或花费的代价。边上带有权的图称为带权图
2.3 线性结构案例解析
/**3.1 顺序栈的实现,后进先出LIFO**/
import java.util.EmptyStackException;
public class SeqStack<T> {
/**
* 栈顶指针,-1代表空栈
*/
private int top=-1;
/**
* 容量大小默认为10
*/
private int capacity=10;
/**
* 存放元素的数组
*/
private T[] array;
private int size;
public SeqStack(int capacity){
array = (T[]) new Object[capacity];
}
public SeqStack(){
array= (T[]) new Object[this.capacity];
}
public int size(){
return size;
}
@Override
public boolean isEmpty() {
return this.top==-1;
}
/**
* 添加元素,从栈顶(数组尾部)插入
* @param data
*/
@Override
public void push(T data) {
//判断容量是否充足
if(array.length==size)
ensureCapacity(size*2+1);//扩容
//从栈顶添加元素
array[++top]=data;
size++;
}
/**
* 获取栈顶元素的值,不删除
* @return
*/
@Override
public T peek() {
if(isEmpty())
new EmptyStackException();
return array[top];
}
/**
* 从栈顶(顺序表尾部)删除
* @return
*/
@Override
public T pop() {
if(isEmpty())
new EmptyStackException();
size--;
return array[top--];
}
/**
* 扩容的方法
* @param capacity
*/
public void ensureCapacity(int capacity) {
//如果需要拓展的容量比现在数组的容量还小,则无需扩容
if (capacity<size)
return;
T[] old = array;
array = (T[]) new Object[capacity];
//复制元素
for (int i=0; i<size ; i++)
array[i]=old[i];
}
//测试一下
public static void main(String[] args){
SeqStack<String> s=new SeqStack<>();
s.push("A");
s.push("B");
s.push("C");
System.out.println("size->"+s.size());
int l=s.size();//size 在减少,必须先记录
for (int i=0;i<l;i++){
System.out.println("s.pop->"+s.pop());
}
System.out.println("s.peek->"+s.peek());
}
}
/**3.2 循环队列的实现,先进先出FIFO**/
public class SeqQueue<T> {
private T elementData[]; //存放元素的数组
private int front,rear; //队头、队尾指针
private int size;
public SeqQueue(){
elementData= (T[]) new Object[10];
front=rear=0;
}
public SeqQueue(int capacity){
elementData= (T[]) new Object[capacity];
front=rear=0;
}
//获取队列的长度
public int size() {
return size;
}
//判断队列是否为空
public boolean isEmpty() {
return front==rear;
}
//入队,可扩容
public boolean add(T data) {
//判断是否满队(满队条件为front=(rear+1)%size,因为队头、队尾至少间隔一个空间)
if (front==(rear+1)%elementData.length){
ensureCapacity(elementData.length*2+1);
}
//添加data
elementData[rear]=data;
//更新rear指向下一个空元素的位置
rear=(rear+1)%elementData.length;
size++;
return true;
}
//返回队头元素,不执行删除操作,若队列为空,返回null
public T peek() {
return elementData[front];
}
//出队,执行删除操作,返回队头元素,若队列为空,返回null
public T poll() {
T temp=this.elementData[this.front];
this.front=(this.front+1)%this.elementData.length;
size--;
return temp;
}
//扩容的方法
public void ensureCapacity(int capacity) {
//如果需要拓展的容量比现在数组的容量还小,则无需扩容
if (capacity<size)
return;
T[] old = elementData;
elementData= (T[]) new Object[capacity];
int j=0;
//复制元素
for (int i=this.front; i!=this.rear ; i=(i+1)%old.length) {
elementData[j++] = old[i];
}
//恢复front,rear指向
this.front=0;
this.rear=j;
}
//测试一下
public static void main(String[] args) {
SeqQueue<String> sq = new SeqQueue<>();
sq.add("A");
sq.add("B");
sq.add("C");
System.out.println("size->"+sq.size());
int l=sq.size(); //size 在减少,必须先记录
for (int i=0;i<l;i++){
System.out.println("sq.poll->"+sq.poll());
}
sq.add("D");
System.out.println("sq.peek->"+sq.peek());
}
}
/**3.3 链表实现**/
//带头节点的链表管理
public class LinkManager {
// 第一个节点,任意赋值,作为头节点使用
private MyData header = new MyData(0);
// (1)在尾部添加结点
public void add(MyData md) {
MyData temp = header;
// 将指针移动到最后
while(temp.next!=null){
temp=temp.next;
}
// 将传进来的节点添加到最后
temp.next = md;
}// add
//(2)遍历节点
public void display() {
// 头节点不用输出:System.out.println(header.data);
MyData temp = header;
while (temp.next != null) {
//跳过头部
temp = temp.next;
System.out.print(temp.data + " ");
} // while
System.out.println();
}
//(3)删除节点:先找节点是否存在,再重新指向
public void del(int data) {
// 存储是否找到
boolean flag = false;
// 从头节点开始找
MyData temp = header;
// 一开始就为null
if (temp.next == null) {
System.out.println("链表为空");
return;
} // if
while(temp.next!=null){
if(data==temp.next.data){
//删除成功
temp.next=temp.next.next;
flag=true;
System.out.println("删除成功!");
break;
}//if
temp=temp.next;
}//while
if(!flag){
System.out.println("没有此节点,无法删除!");
}
}// del
//(4)修改节点的数据,查找整个链表,找节点并修改
public void update(int od,int nd) {
MyData temp = header;
if(temp.next==null){
System.out.println("链表为空!");
return;
}
//当不为空时判断是否有此元素
boolean flag=false;
//循环遍历
while(temp.next!=null){
if(od==temp.next.data){
//改数据
temp.next.data=nd;
System.out.println("修改成功!");
flag=true;
break;
}
temp=temp.next;
}//while
if(!flag){
System.out.println("无此节点,无法修改!");
}
}//update
}
//测试类
public class LinkMain {
public static void main(String[] args) {
LinkManager lm = new LinkManager();
lm.add(new MyData(100));
lm.add(new MyData(98));
lm.add(new MyData(112));
lm.add(new MyData(99));
lm.add(new MyData(108));
lm.add(new MyData(115));
lm.add(new MyData(96));
System.out.println("--------1.添加链表与查询-------");
lm.display();
System.out.println("--------2.删除链表与查询-------");
lm.del(98);
lm.display();
System.out.println("--------3.修改链表与查询-------");
lm.update(100, 888);
lm.display();
}//main
}
2.4 非线性结构案例解析
2.4.1 二叉树的应用
/**2.4.1 二叉树实现**/
//1.定义二叉树节点
public class TreeNode {
//数据
int data;
//左子节点
TreeNode left;
//右子节点
TreeNode right;
//传数据,初始化
public TreeNode(int data){
this.data=data;
}
}
//2.管理二叉树
//添加:创建赋值,查询:前中后序遍历
//待实现:删除/修改
public class TreeManager {
// (1)构建二叉树
public TreeNode createTree() {
// t1起始结点
TreeNode t1 = new TreeNode(1);
TreeNode t2 = new TreeNode(2);
TreeNode t3 = new TreeNode(3);
TreeNode t4 = new TreeNode(4);
TreeNode t5 = new TreeNode(5);
TreeNode t6 = new TreeNode(6);
TreeNode t7 = new TreeNode(7);
TreeNode t8 = new TreeNode(8);
// 从t1(根结点)开始给树中的节点赋左右子节点
// 构建一棵二叉树的效果
t1.left = t2;
t1.right = t3;
t2.left = t4;
t2.right = t5;
t3.left = t6;
t3.right = t7;
t4.left = t8;
// 返回根结点
return t1;
}
// (2)前序遍历二叉树
public void beforeOrder(TreeNode root) {
if (root == null) {
return;
}
// 先输出根结点数据
System.out.print(root.data+"\t");
// 开始递归
// 传根左结点
beforeOrder(root.left);
// 再传根右结点
beforeOrder(root.right);
}
// (3)中序遍历二叉树
public void midOrder(TreeNode root) {
if (root == null) {
return;
}
// 先传根左结点
midOrder(root.left);
// 再输出根结点数据
System.out.print(root.data+"\t");
// 最后传根右结点
midOrder(root.right);
}
//(4)后序遍历二叉树
public void afterOrder(TreeNode root) {
if (root == null) {
return;
}
// 先传根左结点
afterOrder(root.left);
// 再后传根右结点
afterOrder(root.right);
// 最后输出根结点数据
System.out.print(root.data+"\t");
}
}
//3.测式类
public class TreeMain {
public static void main(String[] args) {
// 1.创建二叉树管理对象
TreeManager tm = new TreeManager();
// 2.创建二叉树对象,树的结构画图分析
TreeNode tr = tm.createTree();
// 3.前序遍历、中序遍历、后序遍历
tm.beforeOrder(tr);
System.out.println();
tm.midOrder(tr);
System.out.println();
tm.afterOrder(tr);
}
}
2.4.2 图的应用一
图的应用一
编写程序,输出邻接矩阵如下:
A B C D E
A 0 1 1 0 0
B 1 0 1 1 1
C 1 1 0 0 0
D 0 1 0 0 0
E 0 1 0 0 0
/**2.4.2 图的应用一**/
import java.util.ArrayList;
import java.util.Arrays;
public class Graph {
//(1)构建图
private ArrayList<String> vertexList; // 存储顶点集合
private int[][] edges; // 存储图对应的邻结矩阵
private int numOfEdges; // 表示边的数目
// 构造器,初始化矩阵和vertexList
public Graph(int n) {
edges = new int[n][n];
vertexList = new ArrayList<String>(n);
numOfEdges = 0;
}
// 返回顶点的个数
public int getNumOfVertex() {
return vertexList.size();
}
// 得到边的数目
public int getNumOfEdges() {
return numOfEdges;
}
// 返回结点i(下标)对应的顶点 0->"A" 1->"B" 2->"C"
public String getValueByIndex(int i) {
return vertexList.get(i);
}
// 返回v1和v2的权值
public int getWeight(int v1, int v2) {
return edges[v1][v2];
}
// 显示图对应的矩阵
public void showGraph() {
for (int[] link : edges) {
System.err.println(Arrays.toString(link));
}
}
// 插入结点
public void insertVertex(String vertex) {
vertexList.add(vertex);
}
// 添加边
/**
* @param v1 表示点的下标即使第几个顶点 "A"-"B" "A"->0 "B"->1
* @param v2 第二个顶点对应的下标
* @param weight 表示权重
*/
public void insertEdge(int v1, int v2, int weight) {
// 因为构建的是无向图,所以这里直接设置顶点之间的双向边
edges[v1][v2] = weight;
edges[v2][v1] = weight;
numOfEdges++;
}
//(2)测试一下
public static void main(String[] args) {
// 顶结点的个数
int n = 5;
String Vertexs[] = { "A", "B", "C", "D", "E" };
// 创建图对象
Graph graph = new Graph(n);
// 循环的添加顶点
for (String vertex : Vertexs) {
graph.insertVertex(vertex);
}
// 添加边
// A-B A-C B-C B-D B-E
graph.insertEdge(0, 1, 1); // A-B
graph.insertEdge(0, 2, 1); //
graph.insertEdge(1, 2, 1); //
graph.insertEdge(1, 3, 1); //
graph.insertEdge(1, 4, 1); //
// 获取顶点的个数
System.out.println("顶点个数:"+graph.getNumOfVertex());
// 获取边的数目
System.out.println("边个数:"+graph.getNumOfEdges());
// 获取结点i(下标)对应的顶点 0->"A" 1->"B" 2->"C"
System.out.println("结点下标对应的顶点:"+graph.getValueByIndex(1));
// 获取v1和v2的权值
System.out.println("权值:"+graph.getWeight(1,2));
// 显示邻结矩阵
graph.showGraph();
}
}
2.4.3 图的应用二
图的应用二:深度优先算法DFS(Depth First Search)
遍历图:对节点的访问,有深度优先与广度优先两种遍历方式。
深度优先遍历顺序:1->2->4->8->5->3->6->7
/**
* 2.4.3 图的深度优先遍历算法实现
*/
import java.util.ArrayList;
import java.util.Arrays;
public class Graph {
//(1)构建图
private ArrayList<String> vertexList; // 存储顶点集合
private int[][] edges; // 存储图对应的邻结矩阵
private int numOfEdges; // 表示边的数目
// 定义给数组boolean[], 记录某个结点是否被访问
private boolean[] isVisited;
// 构造器
public Graph(int n) {
// 初始化矩阵和vertexList
edges = new int[n][n];
vertexList = new ArrayList<String>(n);
numOfEdges = 0;
}
// 得到第一个邻接结点的下标 w
/**
* @param index
* @return 如果存在就返回对应的下标,否则返回-1
*/
public int getFirstNeighbor(int index) {
for (int i = 0; i < vertexList.size(); i++) {
if (edges[index][i] > 0) {
return i;
}
}
return -1;
}
// 根据前一个邻接结点的下标来获取下一个邻接结点
/**
* @param v1 当前操作节点
* @param v2 当前操作节点的前一个邻接结点
* @return 下一个邻接结点
*/
public int getNextNeighbor(int v1, int v2) {
//v2是当前操作节点的前一个邻接结点,所以v2+1就是从下一个邻接结点开始找
for (int i = v2 + 1; i < vertexList.size(); i++) {
if (edges[v1][i] > 0) {
return i;
}
}
return -1;
}
// 深度优先遍历算法
// i第一次就是0
private void dfs(boolean[] isVisited, int i) {
// 首先我们访问该结点,输出
System.out.print(vertexList.get(i) + " ");
// 将结点设置为已经访问
isVisited[i] = true;
// 查找结点i的第一个邻接结点w
int w = getFirstNeighbor(i);
while (w != -1) {// 说明有
if (!isVisited[w]) { //并且没有被遍历过
dfs(isVisited, w);
}
// 如果w结点已经被访问过
w = getNextNeighbor(i, w);
}
}
// 对dfs 进行一个重载, 遍历我们所有的结点,并进行 dfs
public void dfs() {
isVisited = new boolean[vertexList.size()];
// 遍历所有的结点,进行dfs[回溯]
for (int i = 0; i < vertexList.size(); i++) {
if (!isVisited[i]) { //遍历过的节点就不再遍历
dfs(isVisited, i);
}
}
}
// 返回结点的个数
public int getNumOfVertex() {
return vertexList.size();
}
// 显示图对应的矩阵
public void showGraph() {
for (int[] link : edges) {
System.err.println(Arrays.toString(link));
}
}
// 得到边的数目
public int getNumOfEdges() {
return numOfEdges;
}
// 返回结点i(下标)对应的数据 0->"A" 1->"B" 2->"C"
public String getValueByIndex(int i) {
return vertexList.get(i);
}
// 返回v1和v2的权值
public int getWeight(int v1, int v2) {
return edges[v1][v2];
}
// 插入结点
public void insertVertex(String vertex) {
vertexList.add(vertex);
}
// 添加边
/**
* @param v1 表示点的下标即使第几个顶点 "A"-"B" "A"->0 "B"->1
* @param v2 第二个顶点对应的下标
* @param weight 表示
*/
public void insertEdge(int v1, int v2, int weight) {
edges[v1][v2] = weight;
edges[v2][v1] = weight;
numOfEdges++;
}
// 测试一下
public static void main(String[] args) {
int n = 8; // 结点的个数
String Vertexs[] = { "1", "2", "3", "4", "5", "6", "7", "8" };
// 创建图对象
Graph graph = new Graph(n);
// 循环的添加顶点
for (String vertex : Vertexs) {
graph.insertVertex(vertex);
}
// 添加边
graph.insertEdge(0, 1, 1);
graph.insertEdge(0, 2, 1);
graph.insertEdge(1, 3, 1);
graph.insertEdge(1, 4, 1);
graph.insertEdge(3, 7, 1);
graph.insertEdge(4, 7, 1);
graph.insertEdge(2, 5, 1);
graph.insertEdge(2, 6, 1);
graph.insertEdge(5, 6, 1);
// 显示邻结矩阵
graph.showGraph();
// 测试dfs遍历
System.out.println("深度遍历");
graph.dfs(); // 1->2->4->8->5->3->6->7
}
}
更多精彩内容请关注本站其他分享!!!