目录
数据结构
数据结构包括:线性结构和非线性结构。
线性结构
- 线性结构作为最常用的数据结构,其特点是数据元素之间存在一对一的线性关系
- 线性结构有两种不同的存储结构,即顺序存储结构和链式存储结构。顺序存储的,线性表称为顺序表,顺序表中的存储元素是连续的
- 链式存储的线性表称为链表,链表中的存储元素不一定是连续的,元素节点中存放数据元素以及相邻元素的地址信息
- 线性结构常见的有:数组、队列、链表和栈
非线性结构
- 非线性结构包括:二维数组,多维数组,广义表,树结构,图结构
稀疏数组
分析问题:
因为该二维数组的很多值是默认值 0, 因此记录了很多没有意义的数据.->稀疏数组
基本介绍
当一个数组中大部分元素为0,或者为同一个值的数组时,可以使用稀疏数组来保存该数组。
稀疏数组的处理方法是:
1)记录数组一共有几行几列,有多少个不同的值
2)把具有不同值的元素的行列及值记录在一个小规模的数组中,从而缩小程序的规模
代码实现
public class SparseArray {
public static void main(String[] args) {
//创建原始11*11的数组
int array[][]=new int[10][11];
array[2][5]=1;
array[4][7]=1;
array[8][7]=1;
//其余为0
for (int []row:array){
for (int data:row){
System.out.print(data+" ");
}
System.out.println("");
}
System.out.println("~~~~~");
//获取非0个数
int sum_NoZero=0;
for (int i=0;i<10;i++){
for (int j=0;j<11;j++){
if (array[i][j]!=0){
sum_NoZero++;
}
}
}
//创建对应是稀疏数组
int sparseArray[][]=new int[sum_NoZero+1][3];
sparseArray[0][0]=10;
sparseArray[0][1]=11;
sparseArray[0][2]=sum_NoZero;
int k=1;
for (int i=0;i<10;i++){
for (int j=0;j<11;j++){
if (array[i][j]!=0){
sparseArray[k][0]=i;
sparseArray[k][1]=j;
sparseArray[k][2]=array[i][j];
k++;
}
}
}
//遍历
for (int []row:sparseArray){
for (int data:row){
System.out.print(data+" ");
}
System.out.println();
}
System.out.println("~~~~~~");
//将稀疏数组转化为原始数组
int [][]array2=new int[sparseArray[0][0]][sparseArray[0][1]];
for (int i=1;i< sparseArray.length;i++){
array2[sparseArray[i][0]][sparseArray[i][1]]=sparseArray[i][2];
}
for (int i=0;i<10;i++){
for (int j=0;j<11;j++){
System.out.print(array2[i][j]+" ");
}
System.out.println();
}
}
}
效果图
链表
单链表
链表(Linked List)介绍
链表是有序的列表,但是它在内存中是存储如下 小结上图:
-
链表是以节点的方式来存储,是链式存储
-
每个节点包含 data 域, next 域:指向下一个节点.
-
如图:发现链表的各个节点不一定是连续存储.
-
链表分带头节点的链表和没有头节点的链表,
单链表(带头结点) 逻辑结构示意图如下
代码实现
public class SingleLinkedList {
//头节点为空
private Node head=new Node(0,"");
//添加节点
public void add(Node node){
Node temp=head;
while (temp.next!=null){
temp=temp.next;
}
temp.next=node;
}
//按id顺序添加节点
public void add_order(Node node){
Node temp=head;
while (temp.next!=null){
//不允许重复
if (temp.next.id==node.id){
System.out.println(node+"添加失败");
return;
}
//找到位置
if (node.id<temp.next.id){
node.next=temp.next;
temp.next=node;
System.out.println(node+"添加成功");
return;
}
temp=temp.next;
}
temp.next=node;
System.out.println(node+"添加成功");
}
public void delete_Node(int id){
Node temp=head;
if (temp.next==null){
System.out.println("该链表为空 删除失败");
return;
}
while (temp.next!=null){
if (temp.next.id==id){
temp.next=temp.next.next;
System.out.println(id+"删除成功");
return;
}
temp=temp.next;
}
System.out.println("delete:无该节点");
}
//修改
public void update(Node node){
Node temp=head;
if (temp.next==null){
System.out.println("该链表为空 修改失败");
return;
}
temp=temp.next;
while (temp!=null){
if (temp.id==node.id){
temp.name=node.name;
System.out.println(node.id+"修改成功");
return;
}
temp=temp.next;
}
System.out.println("无该节点");
}
//展示
public void show(){
Node temp=head.next;
if (temp==null){
System.out.println("show:链表为空");
}else{
System.out.println("~~~展示~~~");
while (temp!=null){
System.out.println(temp);
temp=temp.next;
}
}
}
//节点类
static class Node{
private int id;
private String name;
private Node next;
public Node(int id, String name) {
this.id = id;
this.name = name;
}
@Override
public String toString() {
return "Node{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
public static void main(String[] args) {
SingleLinkedList s=new SingleLinkedList();
s.add_order(new Node(4,"zcj"));
s.add_order(new Node(2,"lhj"));
s.add_order(new Node(2,"wdf"));
s.add_order(new Node(-1,"wzs"));
s.show();
s.update(new Node(2,"lhj2"));
s.show();
// s.delete_Node(5);
// s.delete_Node(-1);
s.delete_Node(2);
s.delete_Node(4);
s.show();
}
}
队列
队列介绍
-
队列是一个有序列表,可以用数组或是链表来实现。
-
遵循先入先出的原则。即:先存入队列的数据,要先取出。后存入的要后取出
数组实现队列
public class ArrayQueueDemo {
private int MaxSize;
//头指针
private int front;
//尾
private int rear;
private int []arr;
public ArrayQueueDemo(int maxSize) {
this.MaxSize = maxSize;
arr=new int[MaxSize];
front=-1; //初始化
rear=-1;
}
public boolean isEmpty(){
return front==rear;
}
public boolean isFull(){
return rear==MaxSize-1;
}
public void addQueue(int n){
if (isFull()){
System.out.println("队列已满 无法添加");
return;
}
rear++;
arr[rear]=n;
System.out.println(n+"添加成功");
}
public int getQueue( ){
if (isEmpty()){
throw new RuntimeException("队列为空,无法获取");
}
front++;
return arr[front];
}
public void show(){
if (isEmpty()){
System.out.println("队列为空");
return;
}
System.out.print("队列数据为:");
for (int i=front+1;i<rear+1;i++){
System.out.print(arr[i]+" ");
}
}
public static void main(String[] args) {
ArrayQueueDemo queueDemo = new ArrayQueueDemo(30);
Scanner scanner = new Scanner(System.in);
System.out.println("输入a:添加");
System.out.println("输入g:获取");
System.out.println("输入s:展示");
System.out.println("输入q:退出");
while (true){
char c = scanner.next().charAt(0);
if (c=='a'){
System.out.print("添加是数据为:");
int n = scanner.nextInt();
queueDemo.addQueue(n);
}else if (c=='g'){
System.out.println("获取的数据为"+queueDemo.getQueue());
}else if (c=='s'){
queueDemo.show();
}else if (c=='q'){
break;
}
}
}
}
但改队列仅能使用一次,需要进行改进
需要实现环形队列
代码:
package com.jie.queue;
import java.util.Scanner;
//环形队列
public class CircleArrayQueue {
private int MaxSize;
//头指针
private int front;
//尾
private int rear;
private int []arr;
public CircleArrayQueue(int maxSize) {
this.MaxSize = maxSize;
arr=new int[MaxSize];
}
public boolean isEmpty(){
return front==rear;
}
public boolean isFull(){
return (rear+1)%MaxSize==front;
}
//添加
public void addQueue(int n){
if (isFull()){
System.out.println("队列已满 无法添加");
return;
}
arr[rear]=n;
rear=(rear+1)%MaxSize;
System.out.println(n+"添加成功");
}
//获取参数
public int getQueue(){
if (isEmpty()){
throw new RuntimeException("队列为空,无法获取");
}
int f=front;
front=(front+1)%MaxSize;
return arr[f];
}
public void show(){
if (isEmpty()){
System.out.println("队列为空");
return;
}
System.out.print("队列数据为:");
if (rear>front){
for (int i=front;i<rear;i++){
System.out.print(arr[i]+" ");
}
}else{
for (int i=front;i<MaxSize;i++){
System.out.print(arr[i]+" ");
}
for (int i=0;i<rear;i++){
System.out.print(arr[i]+" ");
}
}
}
public void show_all(){
for (int i=0;i<arr.length;i++){
System.out.print(arr[i]+" ");
}
}
public static void main(String[] args) {
CircleArrayQueue queueDemo = new CircleArrayQueue(5);
Scanner scanner = new Scanner(System.in);
System.out.println("输入a:添加");
System.out.println("输入g:获取");
System.out.println("输入s:展示");
System.out.println("输入s:展示");
System.out.println("输入S:展示全部");
System.out.println("输入q:退出");
queueDemo.addQueue(0);
queueDemo.addQueue(1);
queueDemo.addQueue(2);
queueDemo.addQueue(3);
while (true){
char c = scanner.next().charAt(0);
System.out.println(queueDemo.front+" "+queueDemo.rear);
if (c=='a'){
System.out.print("添加是数据为:");
int n = scanner.nextInt();
queueDemo.addQueue(n);
}else if (c=='g'){
System.out.println("获取的数据为"+queueDemo.getQueue());
}else if (c=='s'){
queueDemo.show();
}else if (c=='q'){
break;
}else if (c=='S'){
queueDemo.show_all();
}
}
}
}
栈
- 栈的英文为(stack)
- 栈是一个先入后出的有序列表。
- 栈(stack)是限制线性表中元素的插入和删除只能在线性表的同一端进行的一种特殊线性表。允许插入和删除的 一端,为变化的一端,称为栈顶(Top),另一端为固定的一端,称为栈底(Bottom)。
- 根据栈的定义可知,最先放入栈中元素在栈底,最后放入的元素在栈顶,而删除元素刚好相反,最后放入的元 素最先删除,最先放入的元素最后删除
- 图解方式说明出栈(pop)和入栈(push)的概念
代码实现
数组实现
package com.jie.stack;
public class ArrayStack {
private int maxSize;
private int stack[];
private int top=-1;
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;
}
public void push(int value){
if (isFull()){
System.out.println("栈满");
return ;
}
top++;
stack[top]=value;
}
public int pop(){
if (isEmpty()){
throw new RuntimeException("栈空");
}
int value=stack[top];
top--;
return value;
}
public void list(){
if (isEmpty()){
System.out.println("栈空,无法遍历");
return;
}
int temp=top;
for (int i=temp;i>=0;i--){
System.out.println(stack[i]);
}
}
public static void main(String[] args) {
ArrayStack stack=new ArrayStack(5);
stack.list();
stack.push(1);
stack.push(2);
stack.push(3);
stack.push(4);
stack.push(5);
stack.push(6);
stack.pop();
stack.list();
}
}
链表实现
package com.jie.stack;
import com.jie.LinkedList.SingleLinkedList;
public class LinkedListStack {
private int maxSize;
private int top=0;
//头节点
private Node head=new Node(0,"");
public LinkedListStack(int maxSize) {
this.maxSize = maxSize;
}
public Boolean isEmpty(){
return top==-1;
}
public Boolean isFull(){
return top==maxSize;
}
public void push(Node node){
if (!isFull()){
Node temp=head;
while (temp.next!=null){
temp=temp.next;
}
temp.next=node;
top+=1;
}else {
System.out.println("栈满");
}
}
public Node pop(){
if (!isEmpty()){
Node temp=head;
while (temp.next.next!=null){
temp=temp.next;
}
Node n=temp.next;
temp.next=null;
top--;
return n;
}else {
throw new RuntimeException("栈为空");
}
}
//展示
public void show(){
Node temp=head.next;
if (temp==null){
System.out.println("show:链表为空");
}else{
System.out.println("~~~展示~~~");
while (temp!=null){
System.out.println(temp);
temp=temp.next;
}
}
}
//节点类
static class Node{
private int id;
private String name;
private Node next;
public Node(int id, String name) {
this.id = id;
this.name = name;
}
@Override
public String toString() {
return "Node{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
public static void main(String[] args) {
LinkedListStack listStack=new LinkedListStack(5);
listStack.show();
listStack.push(new Node(1,"1"));
listStack.push(new Node(2,"2"));
listStack.push(new Node(3,"3"));
listStack.push(new Node(4,"4"));
listStack.push(new Node(5,"5"));
listStack.push(new Node(5,"5"));
listStack.show();
listStack.pop();
listStack.pop();
listStack.push(new Node(6,"6"));
listStack.show();
}
}
栈实现计算器
package com.jie.stack;
public class NumberStack { //数字栈
private int maxSize;
private float stack[];
private int top=-1;
public NumberStack(int maxSize) {
this.maxSize = maxSize;
stack=new float[this.maxSize];
}
public boolean isFull(){
return top==maxSize-1;
}
public boolean isEmpty(){
return top==-1;
}
public void push(float value){
if (isFull()){
System.out.println("栈满");
return ;
}
top++;
stack[top]=value;
}
public float pop(){
if (isEmpty()){
throw new RuntimeException("栈空");
}
float value=stack[top];
top--;
return value;
}
public void list(){
if (isEmpty()){
System.out.println("栈空,无法遍历");
return;
}
int temp=top;
for (int i=temp;i>=0;i--){
System.out.println(stack[i]);
}
}
public static void main(String[] args) {
String str="1*8-1*3+9/2-5";
NumberStack numberStack=new NumberStack(1000);
SymbolStack symbolStack=new SymbolStack(1000);
StringBuilder num=new StringBuilder();
for (int i = 0; i < str.length(); i++) { //拆分放入数字栈与符号栈
if (str.charAt(i)!='*'&&str.charAt(i)!='/'&&str.charAt(i)!='+'&&str.charAt(i)!='-'){
num.append(str.charAt(i));
if (i==str.length()-1){
numberStack.push(Integer.parseInt(num.toString()));
num=new StringBuilder();
}else if (str.charAt(i+1)=='*'||str.charAt(i+1)=='/'||str.charAt(i+1)=='+'||str.charAt(i+1)=='-'){
numberStack.push(Integer.parseInt(num.toString()));
num=new StringBuilder();
}
}else{
if (str.charAt(i)=='*'){ //如果优先级高 先计算 再放入栈中
float n1=numberStack.pop();
float n2=Integer.parseInt(String.valueOf(str.charAt(++i)));
float value=n1*n2;
numberStack.push(value);
}else if(str.charAt(i)=='/'){
float n1=numberStack.pop();
float n2=Integer.parseInt(String.valueOf(str.charAt(++i)));
numberStack.push(n1/n2);
} else{
symbolStack.push(str.charAt(i));
}
}
}
float result=0;
while (!symbolStack.isEmpty()){ //计算数字栈与符号栈 获取结果
float n1=numberStack.pop();
float n2=numberStack.pop();
char symbol1=symbolStack.pop();
char symbol2=symbolStack.pop();
if (symbol1=='-'){
n1*=-1;
}
if (symbol2=='-'){
n2*=-1;
}
result+=(n1+n2);
if (symbolStack.getTop()==0){
float n3 = numberStack.pop();
float n4 = numberStack.pop();
char symbol3 = symbolStack.pop();
if (symbol3=='-'){
result+=n4-n3;
}else{
result+=n4+n3;
}
}else if (symbolStack.getTop()==-1){
float n3 = numberStack.pop();
result+=n3;
}
}
//输出
System.out.println(result);
}
}
class SymbolStack{ //符号栈
private int maxSize;
private char stack[];
public int getTop() {
return top;
}
private int top=-1;
public SymbolStack(int maxSize) {
this.maxSize = maxSize;
stack=new char[this.maxSize];
}
public boolean isFull(){
return top==maxSize-1;
}
public boolean isEmpty(){
return top==-1;
}
public void push(char value){
if (isFull()){
System.out.println("栈满");
return ;
}
top++;
stack[top]=value;
}
public char pop(){
if (isEmpty()){
throw new RuntimeException("栈空");
}
char value=stack[top];
top--;
return value;
}
public void list(){
if (isEmpty()){
System.out.println("栈空,无法遍历");
return;
}
int temp=top;
for (int i=temp;i>=0;i--){
System.out.println(stack[i]);
}
}
}
哈希表
散列表(Hash table,也叫哈希表)
是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通 过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组 叫做散列表。
结构:数组+链表
//员工
class Emp{
public int id;
public String name;
public Emp next;
public Emp(int id, String name) {
this.id = id;
this.name = name;
}
@Override
public String toString() {
return "Emp{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
//链表
class EmpLinkedList{
private Emp head;
public void add(Emp emp){
if (head==null){
head=emp;
}else {
Emp temp=head;
while (temp.next!=null){
temp=temp.next;
}
temp.next=emp;
}
}
public void list(){
if (head==null){
System.out.print("链表为空");
return;
}
Emp temp=head;
while (temp!=null){
System.out.printf("[id=%d name=%s]->",temp.id,temp.name);
temp=temp.next;
}
}
public Emp findByID(int id){
if (head==null){
return null;
}
Emp temp=head;
while (temp!=null){
if (temp.id==id){
break;
}
temp=temp.next;
}
if (temp==null){
return null;
}else{
return temp;
}
}
}
//hash表
public class HashTab {
private EmpLinkedList[] hash;
private int size;
public HashTab(int size) {
hash=new EmpLinkedList[size];
this.size=size;
for (int i = 0; i < size; i++) {
hash[i]=new EmpLinkedList();
}
}
public void add(Emp emp){
int empList=hashFun(emp.id);
hash[empList].add(emp);
}
public void list(){
for (int i = 0; i < hash.length; i++) {
System.out.printf("%d号链表:",i);
hash[i].list();
System.out.println();
}
}
public void findId(int id){
for (int i = 0; i < hash.length; i++) {
if (hash[i].findByID(id)!=null){
System.out.println("在"+hashFun(id)+"号链表中存在"+hash[i].findByID(id));
return;
}
}
System.out.println("无此id:"+id);
}
public int hashFun(int id){
return id%size;
}
public static void main(String[] args) {
HashTab hashTab=new HashTab(10);
hashTab.add(new Emp(1,"zcj"));
hashTab.add(new Emp(1,"zcj1"));
hashTab.add(new Emp(5,"zcj5"));
hashTab.add(new Emp(55,"zcj55"));
hashTab.add(new Emp(6,"zcj6"));
hashTab.add(new Emp(11,"zcj11"));
hashTab.add(new Emp(12,"zcj12"));
hashTab.list();
hashTab.findId(16);
}
}
树
为什么需要树这种数据结构
数组存储方式的分析
- 优点:通过下标方式访问元素,速度快。对于有序数组,还可使用二分查找提高检索速度。
- 缺点:如果要检索具体某个值,或者插入值(按一定顺序)会整体移动,效率较低 [示意图]
链式存储方式的分析
- 优点:在一定程度上对数组存储方式有优化(比如:插入一个数值节点,只需要将插入节点,链接到链表中即可, 删除效率也很好)。
- 缺点:在进行检索时,效率仍然较低,比如(检索某个值,需要从头节点开始遍历)
链式存储方式的分析
- 能提高数据存储,读取的效率, 比如利用 二叉排序树(Binary Sort Tree),既可以保证数据的检索速度,同时也 可以保证数据的插入,删除,修改的速度。
二叉树
从数据存储来看,数组存储方式和树的存储方式可以相互转换,即数组可以转换成树,树也可以转换成数组,
public class BinaryTree {
private static Node head;
public static void preOrderTraverse(Node node)
{
if(node== null)
return;
else{
System.out.print(node+"->");
preOrderTraverse(node.left);
preOrderTraverse(node.right);
}
}
public static void InOrderTraverse(Node node)
{
if(node== null)
return;
else{
InOrderTraverse(node.left);
System.out.print(node+"->");
InOrderTraverse(node.right);
}
}
public static void PostOrderTraverse(Node node)
{
if(node== null)
return;
else{
PostOrderTraverse(node.left);
PostOrderTraverse(node.right);
System.out.print(node+"->");
}
}
public static void main(String[] args) {
head=new Node(1,"zcj");
Node node2=new Node(2,"zcj2");
Node node3=new Node(3,"zcj3");
Node node4=new Node(4,"zcj4");
Node node5=new Node(5,"zcj5");
Node node6=new Node(6,"zcj6");
head.left=node2;
head.right=node3;
node2.left=node4;
node2.right=node5;
node3.left=node6;
preOrderTraverse(head);
}
}
class Node{
public int id;
public String name;
public Node left;
public Node right;
public Node(int id, String name) {
this.id = id;
this.name = name;
}
@Override
public String toString() {
return "Node{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
赫夫曼数
赫夫曼树是带权路径长度最短的树,权值较大的结点离根较近
创建赫夫曼数
- 从小到大进行排序, 将每一个数据,每个数据都是一个节点 , 每个节点可以看成是一颗最简单的二叉树
- 取出根节点权值最小的两颗二叉树
- 组成一颗新的二叉树, 该新的二叉树的根节点的权值是前面两颗二叉树根节点权值的和
- 再将这颗新的二叉树,以根节点的权值大小 再次排序, 不断重复 1-2-3-4 的步骤,直到数列中,所有的数 据都被处理,就得到一颗赫夫曼树
public class HuffmanTree {
//创建赫夫曼数
public static Node createHuffTree(int arr[]){
List<Node> nodes=new ArrayList<>();
for (int id:arr){
nodes.add(new Node(id));
}
while (nodes.size()>1){
//排序
Collections.sort(nodes);
//获取最小和第二小的节点 创建父节点并且连接
Node leftNode=nodes.get(0);
Node rightNode=nodes.get(1);
Node parent=new Node(leftNode.id+rightNode.id);
parent.left=leftNode;
parent.right=rightNode;
//移除子节点 加入旧节点 并且循环 直到最后一个父节点
nodes.remove(leftNode);
nodes.remove(rightNode);
nodes.add(parent);
}
return nodes.get(0);
}
//前序
public static void preOrderTraverse(Node node)
{
if(node== null)
return;
else{
System.out.print(node+"->");
preOrderTraverse(node.left);
preOrderTraverse(node.right);
}
}
//中序
public static void InOrderTraverse(Node node)
{
if(node== null)
return;
else{
InOrderTraverse(node.left);
System.out.print(node+"->");
InOrderTraverse(node.right);
}
}
//后序
public static void PostOrderTraverse(Node node)
{
if(node== null)
return;
else{
PostOrderTraverse(node.left);
PostOrderTraverse(node.right);
System.out.print(node+"->");
}
}
public static void main(String[] args) {
int arr[]={13,7,8,3,29,6,1};
Node node = createHuffTree(arr);
preOrderTraverse(node);
}
}
class Node implements Comparable<Node>{
public int id;
public String name;
public Node left;
public Node right;
public Node(int id) {
this.id = id;
}
public Node(int id, String name) {
this.id = id;
this.name = name;
}
@Override
public String toString() {
return "Node{" +
"id=" + id +
'}';
}
@Override
public int compareTo(Node o) {
return this.id-o.id;
}
}
图
图是一种数据结构,其中结点可以具有零个或多个相邻元素。两个结点之间的连接称为边。 结点也可以称为 顶点。
图的表示方式有两种:二维数组表示(邻接矩阵);链表表示(邻接表)
邻接矩阵
邻接矩阵是表示图形中顶点之间相邻关系的矩阵,对于 n 个顶点的图而言,矩阵是的 row 和 col 表示的是 1…n 个点
邻接表 1
) 邻接矩阵需要为每个顶点都分配 n 个边的空间,其实有很多边都是不存在,会造成空间的一定损失.
- 邻接表的实现只关心存在的边,不关心不存在的边。因此没有空间浪费,邻接表由数组+链表组成
图的创建
public class Graph {
private ArrayList<String> vertexList; //存放顶点的集合
private int[][] edges; //对应的邻接矩阵
private int numOfEdges; //有多少连线
public Graph(int n){
edges=new int[n][n];
vertexList=new ArrayList<>(n);
numOfEdges=0;
}
//获取连线
public int getNumOfEdges() {
return numOfEdges;
}
//按照图的连线 对邻接矩阵进行编辑
public void insertEdge(int v1,int v2,int weight){
edges[v1][v2]=weight;
edges[v2][v1]=weight;
numOfEdges++;
}
public void insertVertex(String vertex){
vertexList.add(vertex);
}
public int getNumOfVertex(){
return vertexList.size();
}
//根据下标求值
public String getValueByIndex(int i){
return vertexList.get(i);
}
public int getWeight(int v1,int v2){
return edges[v1][v2];
}
public void show(){
for (int i = 0; i < edges.length; i++) {
for (int j = 0; j < edges[0].length; j++) {
System.out.print(edges[i][j]+" ");
}
System.out.println();
}
}
public static void main(String[] args) {
int n=5;
String vertexValue[]={"A","B","C","D","E"};
Graph graph=new Graph(n);
for (String s:vertexValue){
graph.insertVertex(s);
}
//根据图可知 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();
}
}
图遍历介绍
所谓图的遍历,即是对结点的访问。一个图有那么多个结点,如何遍历这些结点,需要特定策略,一般有两种 访问策略:
(1)深度优先遍历
(2)广度优先遍历
DFS (深度优先遍历)
图的深度优先搜索(Depth First Search) 。
-
深度优先遍历,从初始访问结点出发,初始访问结点可能有多个邻接结点,深度优先遍历的策略就是首先访问 第一个邻接结点,然后再以这个被访问的邻接结点作为初始结点,访问它的第一个邻接结点, 可以这样理解: 每次都在访问完当前结点后首先访问当前结点的第一个邻接结点。
-
我们可以看到,这样的访问策略是优先往纵向挖掘深入,而不是对一个结点的所有邻接结点进行横向访问。
-
显然,深度优先搜索是一个递归的过程
public class Graph {
public ArrayList<String> vertexList; //存放顶点的集合
public int[][] edges; //对应的邻接矩阵
public int numOfEdges; //有多少连线
public boolean[] isVisited;
public Graph(int n){
edges=new int[n][n];
vertexList=new ArrayList<>(n);
numOfEdges=0;
isVisited=new boolean[5];
}
//按照图的连线 对邻接矩阵进行编辑
public void insertEdge(int v1,int v2,int weight){
edges[v1][v2]=weight;
edges[v2][v1]=weight;
numOfEdges++;
}
//判断是否有相邻节点
public int getFirstNeighbor(int index){
for (int i = 0; i < vertexList.size(); i++) {
if (edges[index][i]>0){
return i;
}
}
return -1;
}
//查看下个连接节点 是否被连接
public int getNextNeighbor(int v1,int v2){
for (int j = v2+1; j < vertexList.size(); j++) {
if (edges[v1][j]>0){
return j;
}
}
return -1;
}
//深度优先
public void dfs(int i){
System.out.print(vertexList.get(i)+"->");
isVisited[i]=true;
//查看是否有连接节点
int w=getFirstNeighbor(i);
while (w!=-1){
//如果这个连接节点没有被w访问过
if (!isVisited[w]){
//进入递归 查看w是否有连接节点
dfs(w);
}
//继续查看下一个连接节点
w=getNextNeighbor(i,w);
}
}
public void show(){
for (int i = 0; i < edges.length; i++) {
for (int j = 0; j < edges[0].length; j++) {
System.out.print(edges[i][j]+" ");
}
System.out.println();
}
}
public static void main(String[] args) {
int n=5;
String vertexValue[]={"A","B","C","D","E"};
Graph graph=new Graph(n);
for (String s:vertexValue){
graph.vertexList.add(s);
}
//根据图可知 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();
graph.dfs(0);
}
}
BFS (广度优先遍历)
图的广度优先搜索(Broad First Search) 。
类似于一个分层搜索的过程,广度优先遍历需要使用一个队列以保持访问过的结点的顺序,以便按这个顺序来 访问这些结点的邻接结点
public class Graph {
public ArrayList<String> vertexList; //存放顶点的集合
public int[][] edges; //对应的邻接矩阵
public int numOfEdges; //有多少连线
public boolean[] isVisited;
public Graph(int n){
edges=new int[n][n];
vertexList=new ArrayList<>(n);
numOfEdges=0;
isVisited=new boolean[5];
}
//按照图的连线 对邻接矩阵进行编辑
public void insertEdge(int v1,int v2,int weight){
edges[v1][v2]=weight;
edges[v2][v1]=weight;
numOfEdges++;
}
//判断是否有相邻节点
public int getFirstNeighbor(int index){
for (int i = 0; i < vertexList.size(); i++) {
if (edges[index][i]>0){
return i;
}
}
return -1;
}
//查看下个连接节点 是否被连接
public int getNextNeighbor(int v1,int v2){
for (int j = v2+1; j < vertexList.size(); j++) {
if (edges[v1][j]>0){
return j;
}
}
return -1;
}
//广度优先
public void bfs(int i){
int u;
int w;
//定义一个列表
LinkedList<Integer> queue=new LinkedList<>();
System.out.print(vertexList.get(i)+"->");
isVisited[i]=true;
//加入
queue.add(i);
while (!queue.isEmpty()){
//取出头一个
u=queue.removeFirst();
w=getFirstNeighbor(u);
while (w!=-1){
//如果存在u的下个 并且没有访问 即插入
if (!isVisited[w]){
System.out.print(vertexList.get(w)+"->");
isVisited[w]=true;
queue.addLast(w);
}
//重复取u 的下一个
w=getNextNeighbor(u,w);
}
}
}
public void show(){
for (int i = 0; i < edges.length; i++) {
for (int j = 0; j < edges[0].length; j++) {
System.out.print(edges[i][j]+" ");
}
System.out.println();
}
}
public static void main(String[] args) {
int n=5;
String vertexValue[]={"A","B","C","D","E"};
Graph graph=new Graph(n);
for (String s:vertexValue){
graph.vertexList.add(s);
}
//根据图可知 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();
//graph.dfs(0);
graph.bfs(0);
}
}
区别
算法
递归
递归应用场景 看
实际应用场景,迷宫问题(回溯), 递归(Recursion)
递归的概念
简单的说: 递归就是方法自己调用自己,每次调用时传入不同的变量.递归有助于编程者解决复杂的问题,同时 可以让代码变得简洁。
实现迷宫算法
package com.jie.recursion;
public class Migong {
public static void main(String[] args) {
int map[][] = new int[8][7];
for (int i = 0; i < 7; i++) {
map[0][i] = 1;
map[7][i] = 1;
}
for (int i = 0; i < 8; i++) {
map[i][0] = 1;
map[i][6] = 1;
}
map[3][1] = 1;
map[3][2] = 1;
map[2][4] = 1;
map[3][4] = 1;
map[4][4] = 1;
map[5][4] = 1;
map[6][4] = 1;
System.out.println("========地图展示==========");
for (int i = 0; i < map.length; i++) {
for (int j = 0; j < 7; j++) {
System.out.print(map[i][j] + " ");
}
System.out.println();
}
setWay(map,1,1);
System.out.println("========地图展示==========");
for (int i = 0; i < map.length; i++) {
for (int j = 0; j < 7; j++) {
System.out.print(map[i][j] + " ");
}
System.out.println();
}
}
//使用递归回溯来给小球找路
//说明
//1. map 表示地图
//2. i,j 表示从地图的哪个位置开始出发 (1,1)
//3. 如果小球能到 map[6][5] 位置,则说明通路找到. //4. 约定: 当 map[i][j] 为 0 表示该点没有走过 当为 1 表示墙 ; 2 表示通路可以走 ; 3 表示该点已经
//5. 在走迷宫时,需要确定一个策略(方法) 下->右->上->左 , 如果该点走不通,再回溯
public static boolean setWay(int[][] map,int i,int j){
if (map[6][5]==2){
return true;
}else{
if (map[i][j]==0){ //如果此点为0 可以走
map[i][j]=2;
if (setWay(map,i+1,j)){
return true;
}else if (setWay(map,i,j+1)){
return true;
}else if (setWay(map,i-1,j)){
return true;
}else if (setWay(map,i,j-1)){
return true;
}else {
map[i][j]=3;
return false;
}
}else { //如果不等于0 可能是1 2 3
return false;
}
}
}
}
八皇后问题
介绍
八皇后问题,是一个古老而著名的问题,是回溯算法的典型案例。该问题是国际西洋棋棋手马克斯·贝瑟尔于 1848 年提出:在 8×8 格的国际象棋上摆放八个皇后,使其不能互相攻击,即:任意两个皇后都不能处于同一行、 同一列或同一斜线上,
问有多少种摆法:(92)。
思路分析
- 第一个皇后先放第一行第一列
- 第二个皇后放在第二行第一列、然后判断是否 OK, 如果不 OK,继续放在第二列、第三列、依次把所有列都 放完,找到一个合适
- 继续第三个皇后,还是第一列、第二列……直到第 8 个皇后也能放在一个不冲突的位置,算是找到了一个正确 解
- 当得到一个正确解时,在栈回退到上一个栈时,就会开始回溯,即将第一个皇后,放到第一列的所有正确解, 全部得到.
- 然后回头继续第一个皇后放第二列,后面继续循环执行 1,2,3,4 的步骤
public class Queen8 {
private int max=8;
private int count=0;
private int array[]=new int[max]; //一维数组 下标表示行 数值表示列
public void print(){
for (int i = 0; i < array.length; i++) {
System.out.print(array[i]+" ");
}
System.out.println();
}
//判断是否 不冲突
public boolean judge(int n){
for (int i = 0; i < n; i++) {
//判断第n个皇后 是否和前面的皇后产生冲突
if (array[i]==array[n]||Math.abs(n-i)==Math.abs(array[n]-array[i])){
return false;
}
}
return true;
}
public void check(int n){
//n等于8 即
if (n==max){
count++; //符合条件
print();
return;
}
for (int i = 0; i < max; i++) {
array[n]=i; //先在这行放置皇后
if (judge(n)){ //如果不冲突 递归放置下一个皇后
check(n+1);
}
//如果冲突 就尝试下一个位置
//因为 方法中包含循环 每一次放皇后 都会分出8个子方法 进行递归
//直到8个符合要求的放完,或者不符合要求回溯
}
}
public static void main(String[] args) {
Queen8 queen8=new Queen8();
queen8.check(0);
System.out.println("含有"+ queen8.count+"种方法");
}
}
全排列
public class test {
private List<List<Integer>> list=new ArrayList<>();
public static void main(String[] args) {
int[] array = {1,2,3,4};
dfs(array,0);
}
public static void dfs(int[] array, int size) {
if (size==array.length-1){
System.out.println(Arrays.toString(array));
return;
}
for (int i=size;i<array.length;i++){
//先替换
int temp=array[i];
array[i]=array[size];
array[size]=temp;
//递归
dfs(array,size+1);
//回溯
temp=array[i];
array[i]=array[size];
array[size]=temp;
}
}
}
排序算法
介绍
排序也称排序算法(Sort Algorithm),排序是将一组数据,依指定的顺序进行排列的过程。
冒泡排序
基本介绍
冒泡排序(Bubble Sorting)的基本思想是:通过对待排序序列从前向后(从下标较小的元素开始),依次比较 相邻元素的值,若发现逆序则交换,使值较大的元素逐渐从前移向后部,就象水底下的气泡一样逐渐向上冒。
public class BubbleSort {
public static void sort(int arr[]){
for (int i=0;i<arr.length-1;i++){
for (int j=0;j<arr.length-1-i;j++){
if (arr[j]>arr[j+1]){
int temp=arr[j];
arr[j]=arr[j+1];
arr[j+1]=temp;
}
}
}
}
public static void main(String[] args) {
int arr[]={3,9,-1,10,-2};
sort(arr);
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i]+" ");
}
}
}
优化
去除多余的判断
public class BubbleSort {
public static void sort(int arr[]){
boolean flag=false; //如果在一轮循环中没有进行交换 则代表已经排序完成
for (int i=0;i<arr.length-1;i++){
for (int j=0;j<arr.length-1-i;j++){
if (arr[j]>arr[j+1]){
flag=true;
int temp=arr[j];
arr[j]=arr[j+1];
arr[j+1]=temp;
}
}
if (!flag){ //退出
break;
}else { //重新进行下一轮判断
flag=false;
}
}
}
public static void main(String[] args) {
int arr[]={3,9,-1,10,5};
sort(arr);
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i]+" ");
}
}
}
选择排序
package com.jie.sort;
import java.text.SimpleDateFormat;
import java.util.Date;
public class SelectSort {
//80000数据 10s
public static void sort(int arr[]){
for (int i=0;i<arr.length-1;i++){
for (int j=i+1;j<arr.length;j++){
if (arr[i]>arr[j]){
int temp=arr[i];
arr[i]=arr[j];
arr[j]=temp;
}
}
}
}
//80000数据 2s
public static void sort2(int arr[]){ //不需要次次换位置 结束一轮循环 再按照最小数调整位置
for (int i=0;i<arr.length-1;i++){
int minIndex=i;
int min=arr[i];
for (int j=i+1;j<arr.length;j++){
if (min>arr[j]){
minIndex=j;
min=arr[j];
}
}
if (minIndex!=i){
arr[minIndex]=arr[i];
arr[i]=min;
}
}
}
public static void main(String[] args) {
// int arr[]={-101,34,119,1,-1,90,123};
int arr[]=new int[80000];
for (int i = 0; i < 80000; i++) {
arr[i]= (int)(Math.random()*80000);
}
Date date=new Date();
SimpleDateFormat simpleDateFormat=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String before = simpleDateFormat.format(date);
System.out.println(before);
sort(arr);
Date date2=new Date();
String after = simpleDateFormat.format(date2);
System.out.println(after);
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i]+" ");
}
}
}
插入排序
插入排序(Insertion Sorting)
的基本思想是:把 n 个待排序的元素看成为一个有序表和一个无序表,开始时有 序表中只包含一个元素,无序表中包含有 n-1 个元素,排序过程中每次从无序表中取出第一个元素,把它的排 序码依次与有序表元素的排序码进行比较,将它插入到有序表中的适当位置,使之成为新的有序表。
public class InsertSort {
public static void sort(int arr[]){
int j,temp;
for (int i = 0; i < arr.length; i++) {
j=i-1;
temp=arr[i];
while (j>=0&&temp<arr[j]){
arr[j+1]=arr[j];
j--;
}
arr[j+1]=temp;
}
}
public static void main(String[] args) {
int arr[]={3,9,-1,10,5};
sort(arr);
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i]+" ");
}
}
}
希尔排序
希尔排序是希尔(Donald Shell)于 1959 年提出的一种排序算法。希尔排序也是一种插入排序,
它是简单插入排序经过改进之后的一个更高效的版本,也称为缩小增量排序。
交换法
public class ShellSort {
public static void sort(int arr[]){
int step= arr.length/2;
//希尔排序
while (step>0){
for (int i = step; i < arr.length; i++) { //将插入排序分组 按step进行 降低置换次数
for (int j = i-step; j>=0 ; j-=step) {
if (arr[j]>arr[j+step]){
int temp=arr[j];
arr[j]=arr[j+step];
arr[j+step]=temp;
}
}
}
step/=2;
}
}
public static void main(String[] args) {
int arr[]={8,9,1,7,2,3,5,4,6,0};
sort(arr);
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i]+" ");
}
}
}
移动法
因为每次比较都置换的话 就没用使用到插入排序中 改变下标 最后只置换一次的方法,
所以就需要改进成移动法
//移位式
public static void sort2(int arr[]){
int step= arr.length/2;
//希尔排序
while (step>0){
for (int i = step; i < arr.length; i++) { //按step分组进行
int j=i-step;
int temp=arr[i];
if (arr[j]>temp){ //如果在该组内和前面的顺序不对 则进入循环 将temp放在合适的位置
while (j>=0 && temp<arr[j]){
arr[j+step]=arr[j];
j-=step; //寻找合适的位置
}
arr[j+step]=temp; //按照插入排序插入
}
}
step/=2;
}
}
public static void main(String[] args) {
int arr[]={8,9,1,7,2,3,5,4,6,0};
sort2(arr);
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i]+" ");
}
}
快速排序
快速排序(Quicksort)是对冒泡排序的一种改进。
基本思想是:通过一趟排序将要排序的数据分割成独立的两 部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排 序,整个排序过程可以递归进行,以此达到整个数据变成有序序列
public class QuickSort {
public static void sort(int arr[],int left,int right){
if (left>right){
return;
}
int i=left;
int j=right;
int temp=arr[i];
while (i<j){
while (i<j&&arr[j]>=temp){ //因为temp记录的是i的位置 所以要从j开始循环
j--;
}
arr[i]=arr[j];
while (i<j&&arr[i]<=temp){
i++;
}
arr[j]=arr[i];
}
arr[i]=temp;
sort(arr,left,i-1);
sort(arr,i+1,right);
}
public static void main(String[] args) {
int arr[]=new int[1000];
for (int i = 0; i < 1000; i++) {
arr[i]= (int)(Math.random()*1000);
}
sort(arr,0, arr.length-1);
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i]+" ");
}
}
}
归并排序
归并排序(MERGE-SORT)
是利用归并的思想实现的排序方法,该算法采用经典的分治(divide-and-conquer) 策略(分治法将问题分(divide)成一些小的问题然后递归求解,而治(conquer)的阶段则将分的阶段得到的各答案"修 补"在一起,即分而治之)。
public class MergerSort {
//使用递归 进行 先分再治
public static void sort(int []arr,int left,int right, int []temp){
if (left<right){
int mid=(left+right)/2;
sort(arr,left,mid,temp);
sort(arr,mid+1,right,temp);
//合并
merger(arr,left,mid,right,temp);
}
}
/* 合并方法
* @param arr 排序的原始数组
* @param left 左边有序序列的初始索引
* @param mid 中间索引
* @param right 右边索引
* @param temp 做中转的数组
*/
public static void merger(int arr[],int left,int mid,int right,int []temp){
int i=left;
int j=mid+1;
int t=0; //指向temp的索引
//将arr分成[left,mid] [mid+1,right]两组 然后在相互比较 放入temp中
while (i<=mid && j<=right){
if (arr[i]<=arr[j]){
temp[t]=arr[i];
i++;
t++;
}else{
temp[t]=arr[j];
j++;
t++;
}
}
//如果一组已经放完 另外一组依次放入即可
while (i<=mid){
temp[t]=arr[i];
i++;
t++;
}
while (j<=right){
temp[t]=arr[j];
j++;
t++;
}
//将temp放回arr中
t=0;
int tempLeft=left;
while (tempLeft<=right) {
arr[tempLeft] = temp[t];
t++;
tempLeft++;
}
}
public static void main(String[] args) {
int arr[]={8,4,5,7,1,3,6,2};
int temp[]=new int[arr.length];
sort(arr,0, arr.length-1,temp);
System.out.println(Arrays.toString(arr));
}
}
基数排序
基数排序(桶排序)
介绍:
-
将所有待比较数值统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始,依次进行一次排序。 这样从最低位排序一直到最高位排序完成以后, 数列就变成一个有序序列。
-
这样说明,比较难理解,下面我们看一个图文解释,理解基数排序的步骤
图文说明
将数组 {53, 3, 542, 748, 14, 214} 使用基数排序, 进行升序排序
- 按照个十百位的比较进行排序
public class RedixSort {
public static void sort(int arr[]){
//设置十个桶 来放置数据
int [][]bucket=new int[10][arr.length];
//记录每个桶里有效的个数
int bucketElementCounts[]=new int[10];
//求最大数有几位数
int max=0;
for (int i = 0; i < arr.length; i++) {
max=Math.max(max,arr[i]);
}
int length=(max+"").length();
int length_now=0;
//按位数进行循环
while (length_now!=length){
for (int i = 0; i < arr.length; i++) {
int temp=arr[i];
for (int j = 0; j < length_now; j++) {
temp/=10;
}
//获取个位
int k=temp%10;
//存放桶中
bucket[k][bucketElementCounts[k]]=arr[i];
//有效位数++
bucketElementCounts[k]++;
}
//一轮结束后将桶中的数据 放入到桶中
int index=0;
for (int i = 0; i < bucket.length; i++) {
for (int j = 0; j < bucketElementCounts[i]; j++) {
arr[index]=bucket[i][j];
index++;
}
bucketElementCounts[i]=0;
}
length_now++;
}
}
public static void main(String[] args) {
int arr[]={53,3,542,748,14,214};
sort(arr);
System.out.println(Arrays.toString(arr));
}
}
堆排序
堆排序是利用堆这种数据结构而设计的一种排序算法,堆排序是一种选择排序,它的最坏,最好,平均时间复 杂度均为 O(nlogn),它也是不稳定排序。
堆是具有以下性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆
堆排序基本思想
堆排序的基本思想是:
- 将待排序序列构造成一个大顶堆
- 此时,整个序列的最大值就是堆顶的根节点。
- 将其与末尾元素进行交换,此时末尾就为最大值。
- 然后将剩余 n-1 个元素重新构造成一个堆,这样会得到 n 个元素的次小值。如此反复执行,便能得到一个有序 序列了。 可以看到在构建大顶堆的过程中,元素的个数逐渐减少,最后就得到一个有序序列了
public class HeapSort {
public static void sort(int arr[]){
//变为大顶堆
for (int i = arr.length/2-1; i >=0 ; i--) {
adjustHeap(arr,i, arr.length);
}
System.out.println(Arrays.toString(arr));
//首先将这轮循环的最大数沉到最后
int temp=0;
for (int i = arr.length-1; i >0; i--) {
temp=arr[i];
arr[i]=arr[0];
arr[0]=temp;
adjustHeap(arr,0, i); //在排除沉到最后的数后 继续求出大顶堆
}
}
//将一个数组(二叉树), 调整成一个大顶堆
/**
* 功能: 完成 将 以 i 对应的非叶子结点的树调整成大顶堆
* 举例 int arr[] = {4, 6, 8, 5, 9}; => i = 1 => adjustHeap => 得到 {4, 9, 8, 5, 6}
* 如果我们再次调用 adjustHeap 传入的是 i = 0 => 得到 {4, 9, 8, 5, 6} => {9,6,8,5, 4}
* arr 待调整的数组
* i 表示非叶子结点在数组中索引
* length 表示对多少个元素继续调整, length 是在逐渐的减少
*/
public static void adjustHeap(int arr[],int i,int length){
int temp=arr[i];
for (int k=i*2+1;k<length;k=k*2+1){ //k为i的左子节点
if(k+1<length && arr[k]<arr[k+1]){ //比较左子节点 和 右子节点
k++; //讲k指向右子节点
}
if (arr[k]>temp){ //如果左右子节点 比父节点 大 交换
arr[i]=arr[k];
i=k; //将i变为较大子节点的位置
}else {
break;
}
}
//替换temp
arr[i]=temp;
}
public static void main(String[] args) {
int arr[]={4,6,8,5,9,-1,11};
sort(arr);
System.out.println(Arrays.toString(arr));
}
}
对比
相关术语解释:
- 稳定:如果 a 原本在 b 前面,而 a=b,排序之后 a 仍然在 b 的前面;
- 不稳定:如果 a 原本在 b 的前面,而 a=b,排序之后 a 可能会出现在 b 的后面;
- 内排序:所有排序操作都在内存中完成;
- 外排序:由于数据太大,因此把数据放在磁盘中,而排序通过磁盘和内存的数据传输才能进行;
- 时间复杂度: 一个算法执行所耗费的时间。
- 空间复杂度:运行完一个程序所需内存的大小。
- n: 数据规模
- k: “桶”的个数
- In-place: 不占用额外内存
- Out-place: 占用额外内存
较快的有 希尔排序 快速排序 归并排序 基数排序 堆排序
其中 基数排序 是用空间换时间
查找算法
在 java 中,我们常用的查找有四种:
-
顺序(线性)查找
-
二分查找/折半查找
-
插值查找
-
斐波那契查找
线性查找
有一个数列 ,判断数列中是否包含此名称【顺序查找】 要求: 如果找到了,就提 示找到,并给出下标值
public class SeqSearch {
public static int search(int arr[],int value){
for (int i = 0; i < arr.length; i++) {
if (arr[i]==value){
return i;
}
}
return -1;
}
public static void main(String[] args) {
int arr[]={1,9,11,-1,34,89};
int index = search(arr, -1);
if (index==-1){
System.out.println("无此数");
}else{
System.out.println("下标为"+index);
}
}
}
二分查找
必须是排序好的数组
public class BinarySearch {
public static int search(int []arr,int value){
int left=0;
int right=arr.length-1;
int mid;
while (right>=left){
mid=(left+right)/2;
if (arr[mid]<value){
left=mid+1;
}else if(arr[mid]>value){
right=mid-1;
}else {
return mid;
}
}
return -1;
}
public static void main(String[] args) {
int arr[]={1,2,3,4,5,6};
int index = search(arr, 2);
if (index==-1){
System.out.println("无此数");
}else{
System.out.println("下标为"+index);
}
}
}
插值查找
插值查找原理介绍: 插值查找算法类似于二分查找,不同的是插值查找每次从自适应 mid 处开始查找。
使mid进行一些优化
对应前面的代码公式:
int mid = left + (right – left) * (findVal – arr[left]) / (arr[right] – arr[left])
代码
public class BinarySearch {
public static int search(int []arr,int value){
int left=0;
int right=arr.length-1;
int mid;
while (right>=left){
mid = left + (right - left) * (value - arr[left]) / (arr[right] - arr[left]);
if (arr[mid]<value){
left=mid+1;
}else if(arr[mid]>value){
right=mid-1;
}else {
return mid;
}
}
return -1;
}
public static void main(String[] args) {
int arr[]=new int[100];
for (int i = 0; i < 100; i++) {
arr[i]=i;
}
int index = search(arr, 88);
if (index==-1){
System.out.println("无此数");
}else{
System.out.println("下标为"+index);
}
}
}
斐波那契查找
斐波那契查找原理与前两种相似,仅仅改变了中间结点(mid)的位置,mid 不再是中间或插值得到,而是位 于黄金分割点附近,
即 mid=low+F(k-1)-1(F 代表斐波那契数列),如下图所示
public class FibonacciSearch {
public static int fib(int n){ //斐波那契递归算法
if (n==1||n==0){
return 1;
}
return fib(n-1)+fib(n-2);
}
public static int search(int arr[],int value){
int left=0,right=arr.length-1;
int k=0;
int mid=0;
while (right>fib(k)-1){
k++;
}
int[] temp = Arrays.copyOf(arr, fib(k));
for(int i = right + 1; i < temp.length; i++) {
temp[i] = arr[right];
}
while (left<=right){
System.out.println(k);
mid=left+fib(k-1)-1; //利用斐波那契求 mid
if (temp[mid]<value){
left=mid+1;
k-=2;
}else if(temp[mid]>value){
right=mid-1;
k--;
}else {
if (mid<=right){
return mid;
}else{
return right;
}
}
}
return -1;
}
public static void main(String[] args) {
int arr[]={1,8,10,89,1000,1234};
int index = search(arr, 1234);
if (index==-1){
System.out.println("无此数");
}else{
System.out.println("下标为"+index);
}
}
}
汉诺塔
public class Hanoitower {
public static int count=0;
public static void hanoiTower(int num,char a,char b,char c){
if (num==1){
System.out.println(++count+":将第"+num+"个盘移动:"+a+"->"+c);
}else{
hanoiTower(num-1,a,c,b);
System.out.println(++count+":将第"+num+"个盘移动:"+a+"->"+c);
hanoiTower(num-1,b,a,c);
}
}
public static void main(String[] args) {
hanoiTower(3,'A','B','C');
}
}
动态规划算法
- 动态规划(Dynamic Programming)算法的核心思想是:将大问题划分为小问题进行解决,从而一步步获取最优解 的处理算法
- 动态规划算法与分治算法类似,其基本思想也是将待求解问题分解成若干个子问题,先求解子问题,然后从这 些子问题的解得到原问题的解。
- 与分治法不同的是,适合于用动态规划求解的问题,经分解得到子问题往往不是互相独立的。 ( 即下一个子 阶段的求解是建立在上一个子阶段的解的基础上,进行进一步的求解 )
- 动态规划可以通过填表的方式来逐步推进,得到最优解.
思路
public class KnapasackProblem {
public static void main(String[] args) {
int w[]={1,4,3};
int val[]={1500,3000,2000};
int m=4; //背包的容量
int n= val.length; //物品的个数
int [][]v=new int[n+1][m+1];
//第1列 和第1行都为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 {
//否则 计算上一行的值 和计算的的值的最大数
int n1=v[i-1][j];
//n2代表 此产品的价值+剩余空间能存放的最大价值的和
int n2=val[i-1]+v[i-1][j-w[i-1]];
v[i][j]=Math.max(n1,n2);
}
}
}
for (int i = 0; i < v.length; i++) {
System.out.println(Arrays.toString(v[i]));
}
}
}
如果需要输出书包存放的内容 则需要改进
public class KnapasackProblem {
public static void main(String[] args) {
int w[]={1,4,3};
int val[]={1500,3000,2000};
int m=4; //背包的容量
int n= val.length; //物品的个数
int [][]v=new int[n+1][m+1];
int[][] path = new int[n+1][m+1];
//第1列 和第1行都为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 {
//否则 计算上一行的值 和计算的的值的最大数
int n1=v[i-1][j];
//n2代表 此产品的价值+剩余空间能存放的最大价值的和
int n2=val[i-1]+v[i-1][j-w[i-1]];
if (n1<n2){
v[i][j]=n2;
//对插入点进行标记
path[i][j]=1;
}else{
v[i][j]=n1;
}
}
}
}
for (int i = 0; i < v.length; i++) {
System.out.println(Arrays.toString(v[i]));
}
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.printf("将%d个商品放入背包\n",i);
//容量减少
j-=w[i-1];
}
i--;
}
}
}
KMP算法
有一个字符串 Str1 = “BBC ABCDAB ABCDABCDABDE”,判断,里面是否包含另一个字符串 Str2 = “ABCDABD”?
- 首先获取需要搜索的部分匹配值
- 然后再去匹配字符串
- 如果出现不匹配 则就需要根据部分匹配表 判断下个匹配的位置
public class KMP {
public static void main(String[] args) {
String str1 = "BBC ABCDAB ABCDABCDABDE";
String str2 = "ABCDABD";
int[] next = kmpNext(str2);
System.out.println(kmpSearch(str1, str2, next));
}
//获取字符串的部分匹配值
public static int[] kmpNext(String str){
//存放部分匹配值
int next[]=new int[str.length()];
next[0]=0;
for (int i = 1,j=0; i <str.length() ; i++) {
//如果j>0 且不匹配i位置的数 则需要后退进行再次匹配 知道j为0
while (j>0 && str.charAt(i)!=str.charAt(j)){
j=next[j-1];
}
if (str.charAt(i)==str.charAt(j)){
j++;
}
//记录
next[i]=j;
}
return next;
}
//KMP
public static int kmpSearch(String str1,String str2,int []next){
//i代表 str1的下标
//j代表 str2的下标
for (int i=0,j=0;i<str1.length();i++){
//根据 部分匹配值 来获取继续匹配的位置
while (j>0&&str1.charAt(i)!=str2.charAt(j)){
j=next[j-1];
}
if(str1.charAt(i)==str2.charAt(j)){
j++;
}
if (j==str2.length()){
return i-j+1;
}
}
return -1;
}
}
贪心算法
- 贪婪算法(贪心算法)是指在对问题进行求解时,在每一步选择中都采取最好或者最优(即最有利)的选择,从而 希望能够导致结果是最好或者最优的算法
- 贪婪算法所得到的结果不一定是最优的结果(有时候会是最优解),但是都是相对近似(接近)最优解的结果
- 首先需要一个 HashMap 记录 广播台:地区
- 然后需要HashSet 保存需要覆盖的地区
- 然后进入循环 ,计算当前这个k与 地区的有效交集
- 然后比较有效交集最大的那个
- 知道地区取完为空
public class Greedy {
public static ArrayList<String> greedy(HashMap<String, HashSet<String>> broadcasts,HashSet<String> allAreas){
//创建ArrayList 存放选择的电台
ArrayList<String> selects=new ArrayList<>();
//定义临时集合 存放遍历过程中
HashSet<String> tempSet=new HashSet<>();
String maxKey=null;
//max记录覆盖的最大值 size记录覆盖的当前值
int max_size=0,size=0;
while (allAreas.size()!=0){
for (String key:broadcasts.keySet()){
//当前key能覆盖的地区
HashSet<String> areas = broadcasts.get(key);
//添加到临时集合
tempSet.addAll(areas);
//求与全部地区的交集
tempSet.retainAll(allAreas);
//计算当前覆盖的大小
size=tempSet.size();
if (tempSet.size()>0 && (maxKey==null||size>max_size)){ //如果当前的大小比最大值的要大的话 则进行覆盖
maxKey=key;
max_size=size;
}
tempSet.clear();
}
if (maxKey!=null){
selects.add(maxKey);
allAreas.removeAll(broadcasts.get(maxKey));
}
maxKey=null;
max_size=0;
}
return selects;
}
public static void main(String[] args) {
HashMap<String, HashSet<String>> broadcasts=new HashMap<>();
//将各个电台放入到 broadcasts
HashSet<String> hashSet1 = new HashSet<String>();
hashSet1.add("北京");
hashSet1.add("上海");
hashSet1.add("天津");
HashSet<String> hashSet2 = new HashSet<String>();
hashSet2.add("广州");
hashSet2.add("北京");
hashSet2.add("深圳");
HashSet<String> hashSet3 = new HashSet<String>();
hashSet3.add("成都");
hashSet3.add("上海");
hashSet3.add("杭州");
HashSet<String> hashSet4 = new HashSet<String>();
hashSet4.add("上海");
hashSet4.add("天津");
HashSet<String> hashSet5 = new HashSet<String>();
hashSet5.add("杭州");
hashSet5.add("大连");
//加入到 map
broadcasts.put("K1", hashSet1);
broadcasts.put("K2", hashSet2);
broadcasts.put("K3", hashSet3);
broadcasts.put("K4", hashSet4);
broadcasts.put("K5", hashSet5);
//存放 所有地区
HashSet<String> allAreas=new HashSet<>();
allAreas.add("北京");
allAreas.add("上海");
allAreas.add("天津");
allAreas.add("广州");
allAreas.add("深圳");
allAreas.add("成都");
allAreas.add("杭州");
allAreas.add("大连");
System.out.println(greedy(broadcasts,allAreas));
}
}
普利姆算法
-
- 有胜利乡有 7 个村庄(A, B, C, D, E, F, G) ,现在需要修路把 7 个村庄连通
-
- 各个村庄的距离用边线表示(权) ,比如 A – B 距离 5 公里
-
- 问:如何修路保证各个村庄都能连通,并且总的修建公路总里程最短?
思路: 将 10 条边,连接即可,但是总的里程数不是最小. 正确的思路,就是尽可能的选择少的路线,并且每条路线最小,保证总里程数最少
普利姆(Prim)算法求最小生成树,也就是在包含 n 个顶点的连通图中,找出只有(n-1)条边包含所有 n 个顶点的 连通子图,也就是所谓的极小连通子图
- 从A开始 与连接且为标记的地点进行判断 输出最小的路 (G)
- 将 A、G都去寻找连接且未标记的路
- 知道最后所有都被标记 即为最短的权值和
public class Prim {
public void createGraph(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 showGraph(Graph graph){
for (int []link:graph.weight){
System.out.println(Arrays.toString(link));
}
}
public void prim(Graph graph,int v){
//标记 改点是否被访问过
int visited[]=new int[graph.verxs];
visited[v]=1;
int h1=-1;
int h2=-1;
int minWeight=10000;
for (int k=1;k<graph.verxs;k++){ //求长度-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){ //i表示已经访问的点 j表示为访问的点
minWeight=graph.weight[i][j];
h1=i;
h2=j;
}
}
}
System.out.println("边<"+graph.data[h1]+","+graph.data[h2]+">权值"+minWeight);
visited[h2]=1; //将该节点标记为已访问
minWeight=10000; //权值改回默认
}
}
public static void main(String[] args) {
char[] data = new char[]{'A','B','C','D','E','F','G'};
int verxs = data.length;
int [][]weight=new int[][]{
{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);
Prim prim=new Prim();
prim.createGraph(graph,verxs,data,weight);
prim.showGraph(graph);
prim.prim(graph,0);
}
}
class Graph{
char data[];
int verxs;
int weight[][];
public Graph(int verxs){
this.verxs=verxs;
data=new char[verxs];
weight=new int[verxs][verxs];
}
}
克鲁斯卡尔算法
思路
- 选择权值最小的路径
- 如果与之前的加入的没有形成回路 就加入
- 循环前两步 直到最后全部连接
//图 存放邻接矩阵和 和 节点列表
class Graph{
char data[];
int length;
int weight[][];
public Graph(int length){
this.length=length;
data=new char[length];
weight=new int[length][length];
}
}
//表示 连接线 【 起点,终点 ,权值】
class EData implements Comparable<EData>{
char start;
char end;
int weight;
public EData(char start, char end, int weight) {
this.start = start;
this.end = end;
this.weight = weight;
}
@Override
public String toString() {
return
"(start=" + start +
", end=" + end +
", weight=" + weight +")";
}
@Override
public int compareTo(EData o) {
return this.weight-o.weight;
}
}
//实现克鲁斯卡尔算法
public class Kruskal {
private static final int INF=Integer.MAX_VALUE;
//记录有效的连接
public int edgeNum=0;
//保存图
public Graph graph;
public void createGraph(int length, char data[], int [][]weight){
//创建并赋值
graph=new Graph(data.length);
for (int i = 0; i < length; i++) {
graph.data[i]=data[i];
for (int j = 0; j < length; j++) {
graph.weight[i][j]=weight[i][j];
}
}
//求有效边
for (int i = 0; i < length; i++) {
for (int j = i+1; j < length; j++) {
if (graph.weight[i][j]!=INF){
edgeNum++;
}
}
}
}
//创建有效边集合
public EData[] getEdges(){
int index=0;
EData datas[]=new EData[edgeNum];
for (int i = 0; i <graph.data.length ; i++) {
for (int j = i+1; j < graph.data.length; j++) {
if (graph.weight[i][j]!=INF){
datas[index++]=new EData(graph.data[i],graph.data[j],graph.weight[i][j]);
}
}
}
return datas;
}
//对有效边进行排序
public void sortEdges(EData[] eData){
Arrays.sort(eData);
}
public void showGraph(){
for (int []link:graph.weight){
System.out.println(Arrays.toString(link));
}
}
//寻找下标
public int getPosition(char ch){
for (int i = 0; i < graph.data.length; i++) {
if (graph.data[i]==ch){
return i;
}
}
return -1;
}
//获取连接的最终终点的位置
public int getEnd(int []ends,int i){
while (ends[i]!=0){ //如果不是0 则后面还有连接 循环到最后一个
i=ends[i];
}
return i;
}
public void kruskal(){
int index=0;
int []ends=new int[edgeNum]; //记录终点坐标
EData[] rets=new EData[edgeNum]; //记录最终结果的边
//获取有效连接集合
EData[] edges=getEdges();
//排序
sortEdges(edges);
System.out.println(Arrays.toString(edges));
//判断是否构成回路
for (int i = 0; i < edgeNum; i++) {
int p1=getPosition(edges[i].start);
int p2=getPosition(edges[i].end);
//获得start的终点 获得end的终点
int m=getEnd(ends,p1);
int n=getEnd(ends,p2);
if (m!=n){
ends[m]=n;
rets[index++]=edges[i];
}
}
for (int i = 0; i < index; i++) {
System.out.print(rets[i]+"->");
}
}
public static void main(String[] args) {
char[] data = {'A', 'B', 'C', 'D', 'E', 'F', 'G'};
int length=data.length;
int weight[][] = {
{ 0, 12, INF, INF, INF, 16, 14},
{ 12, 0, 10, INF, INF, 7, INF},
{ INF, 10, 0, 3, 5, 6, INF},
{ INF, INF, 3, 0, 4, INF, INF},
{ INF, INF, 5, 4, 0, 2, 8},
{ 16, 7, 6, INF, 2, 0, 9},
{ 14, INF, INF, INF, 8, 9, 0}};
Kruskal kruskal=new Kruskal();
kruskal.createGraph(length,data,weight);
kruskal.showGraph();
kruskal.kruskal();
}
}
骑士周游算法
马踏棋盘算法介绍和游戏演示
-
马踏棋盘算法也被称为骑士周游问题
-
将马随机放在国际象棋的 8×8 棋盘 Board[0~7][0~7]的某个方格中,马按走棋规则(马走日字)进行移动。要求 每个方格只进入一次,走遍棋盘上全部 64 个方格
采用深度优先
public class HorseChess {
private static int X;
private static int Y;
private static int chessboard[][];
private static boolean visited[][];
private static boolean finished;
public static void traversal(int row,int column,int step){
chessboard[row][column]=step;
//标记
visited[row][column]=true;
//获取下一步能走的集合
ArrayList<Point> next = next(new Point(row, column)); //point 的x代表列 y代表行
//只要该点还有可访问 便递归 否则回溯
while (!next.isEmpty()){
Point p = next.remove(0);
if (!visited[p.x][p.y]){
traversal(p.x,p.y,step+1);
}
}
//查看是否满足条件
if (step<X*Y&&!finished){
chessboard[row][column]=0;
visited[row][column]=false;
}else {
finished=true;
}
}
public static ArrayList<Point> next(Point curPoint){
ArrayList<Point> ps=new ArrayList<>();
Point p1=new Point();
if((p1.x = curPoint.x - 2) >= 0 && (p1.y = curPoint.y -1) >= 0) {
ps.add(new Point(p1));
}
if((p1.x = curPoint.x - 1) >=0 && (p1.y=curPoint.y-2)>=0) {
ps.add(new Point(p1));
}
if ((p1.x = curPoint.x + 1) < X && (p1.y = curPoint.y - 2) >= 0) {
ps.add(new Point(p1));
}
if ((p1.x = curPoint.x + 2) < X && (p1.y = curPoint.y - 1) >= 0) {
ps.add(new Point(p1));
}
if ((p1.x = curPoint.x + 2) < X && (p1.y = curPoint.y + 1) < Y) {
ps.add(new Point(p1));
}
if ((p1.x = curPoint.x + 1) < X && (p1.y = curPoint.y + 2) < Y) {
ps.add(new Point(p1));
}
if ((p1.x = curPoint.x - 1) >= 0 && (p1.y = curPoint.y + 2) < Y) {
ps.add(new Point(p1));
}
if ((p1.x = curPoint.x - 2) >= 0 && (p1.y = curPoint.y + 1) < Y) {
ps.add(new Point(p1));
}
return ps;
}
public static void main(String[] args) {
X=8;
Y=8;
chessboard=new int[X][Y];
int row=0,column=0;
visited=new boolean[X][Y];
long start = System.currentTimeMillis();
traversal(row , column , 1);
long end = System.currentTimeMillis();
System.out.println("共耗时: " + (end - start) /1000+ " 秒");
for (int rows[]:chessboard){
for (int step:rows){
System.out.printf("%3d",step);
}
System.out.println();
}
}
}
如果单纯使用深度优先 则会考虑很多情况 可以对next 进行改进
按从小到大排序 减少回溯的可能
public class HorseChess {
private static int X;
private static int Y;
private static int chessboard[][];
private static boolean visited[][];
private static boolean finished;
public static void traversal(int row,int column,int step){
chessboard[row][column]=step;
//标记
visited[row][column]=true;
//获取下一步能走的集合
ArrayList<Point> next = next(new Point(row, column)); //point 的x代表列 y代表行
sort(next);
//只要该点还有可访问 便递归 否则回溯
while (!next.isEmpty()){
Point p = next.remove(0);
if (!visited[p.x][p.y]){
traversal(p.x,p.y,step+1);
}
}
//查看是否满足条件
if (step<X*Y&&!finished){
chessboard[row][column]=0;
visited[row][column]=false;
}else {
finished=true;
}
}
public static ArrayList<Point> next(Point curPoint){
ArrayList<Point> ps=new ArrayList<>();
Point p1=new Point();
if((p1.x = curPoint.x - 2) >= 0 && (p1.y = curPoint.y -1) >= 0) {
ps.add(new Point(p1));
}
if((p1.x = curPoint.x - 1) >=0 && (p1.y=curPoint.y-2)>=0) {
ps.add(new Point(p1));
}
if ((p1.x = curPoint.x + 1) < X && (p1.y = curPoint.y - 2) >= 0) {
ps.add(new Point(p1));
}
if ((p1.x = curPoint.x + 2) < X && (p1.y = curPoint.y - 1) >= 0) {
ps.add(new Point(p1));
}
if ((p1.x = curPoint.x + 2) < X && (p1.y = curPoint.y + 1) < Y) {
ps.add(new Point(p1));
}
if ((p1.x = curPoint.x + 1) < X && (p1.y = curPoint.y + 2) < Y) {
ps.add(new Point(p1));
}
if ((p1.x = curPoint.x - 1) >= 0 && (p1.y = curPoint.y + 2) < Y) {
ps.add(new Point(p1));
}
if ((p1.x = curPoint.x - 2) >= 0 && (p1.y = curPoint.y + 1) < Y) {
ps.add(new Point(p1));
}
return ps;
}
//对next进行排序 减少回溯的可能
public static void sort(ArrayList<Point> next){
next.sort(new Comparator<Point>() {
@Override
public int compare(Point o1, Point o2) {
int count1=next(o1).size();
int count2=next(o2).size();
if (count1<count2){
return -1;
}else if (count1==count2){
return 0;
}else{
return 1;
}
}
});
}
public static void main(String[] args) {
X=8;
Y=8;
chessboard=new int[X][Y];
int row=0,column=0;
visited=new boolean[X][Y];
long start = System.currentTimeMillis();
traversal(row , column , 1);
long end = System.currentTimeMillis();
System.out.println("共耗时: " + (end - start) /1000+ " 秒");
for (int rows[]:chessboard){
for (int step:rows){
System.out.printf("%3d",step);
}
System.out.println();
}
}
}