数据结构
数据结构包括:线性结构和非线性结构。
线性结构
- 线性结构作为最常用的数据结构,其特点是数据元素之间存在一对一的线性关系
- 线性结构有两种不同的存储结构,即顺序存储结构和链式存储结构。顺序存储的线性表称为顺序表,顺序表中的存储元素是连续的
- 链式存储的线性表称为链表,链表中的存储元素不一定是连续的,元素节点中存放数据元素以及相邻元素的地址信息
- 线性结构常见的有:数组、队列、链表和栈,后面我们会详细讲解.
非线性结构
- 非线性结构包括:二维数组,多维数组,广义表,树结构,图结构
稀疏数组
基本介绍
当一个数组中大部分元素为0,或者为同一个值的数组时,可以使用稀疏数组来保存该数组。
稀疏数组的处理方法是:
- 记录数组一共有几行几列,有多少个不同的值
- 把具有不同值的元素的行列及值记录在一个小规模的数组中,从而缩小程序的规模
应用实例
- 使用稀疏数组,来保留类似前面的二维数组(棋盘、地图等等)
- 把稀疏数组存盘,并且可以从新恢复原来的二维数组数
整体思路分析
package dataStructure;
public class SparseArray {
public static void main(String[] args) {
int[][] chessArray = new int[11][11];
chessArray[1][2]=1;
chessArray[2][3]=2;
for(int[] row:chessArray){
for (int element:row) {
System.out.print("\t"+element);
}
System.out.println();
}
int sum=0;
for (int i = 0; i < chessArray.length; i++) {
for (int j = 0; j < chessArray.length; j++) {
if (chessArray[i][j]!=0){
sum++;
}
}
}
System.out.println("sum="+sum);
int[][] sparseArray=new int[sum+1][3];
sparseArray[0][0]= chessArray.length;
sparseArray[0][1]= chessArray.length;
sparseArray[0][2]=sum;
int count=1;
for (int i = 0; i < chessArray.length; i++) {
for (int j = 0; j < chessArray.length; j++) {
if (chessArray[i][j]!=0){
sparseArray[count][0]=i;
sparseArray[count][1]=j;
sparseArray[count][2]=chessArray[i][j];
count++;
}
}
}
System.out.println("稀疏数组");
for (int[] row:sparseArray){
for (int element:row){
System.out.print("\t"+element);
}
System.out.println();
}
System.out.println("原型数组");
int[][] chessArray1=new int[sparseArray[0][0]][sparseArray[0][1]];
for (int i = 1; i < sparseArray.length; i++) {
chessArray1[sparseArray[i][0]][sparseArray[i][1]]=sparseArray[i][2];
}
for (int[] row:chessArray1){
for (int element:row){
System.out.print("\t"+element);
}
System.out.println();
}
}
}
队列
队列介绍
- 队列是一个有序列表,可以用数组或是链表来实现。
- 遵循先入先出的原则。即:先存入队列的数据,要先取出。后存入的要后取出
数组模拟队列
- 队列本身是有序列表,若使用数组的结构来存储队列的数据,则队列数组的声明如下图, 其中 maxSize 是该队列的最大容量。
- 因为队列的输出、输入是分别从前后端来处理,因此需要两个变量 front及 rear分别记录队列前后端的下标,front 会随着数据输出而改变,而 rear则是随着数据输入而改变
基本思想
- 将尾指针往后移:rear+1 , 当front == rear 【空】
- 若尾指针 rear 小于队列的最大下标 maxSize-1,则将数据存入 rear所指的数组元素中,否则无法存入数据。 rear ==maxSize - 1[队列满]
package dataStructure;
import java.util.Scanner;
public class Queue {
public static void main(String[] args) {
Scanner s=new Scanner(System.in);
boolean loop=true;
ArrayQueue arrayQueue=new ArrayQueue(3);
while (loop){
System.out.println("1.显示队列"+
"\n2.添加数据"+
"\n3.取出数据"+
"\n4.查看头数据"+
"\n5.退出");
int choice=s.nextInt();
switch (choice){
case 1:
arrayQueue.showQueue();
break;
case 2:
int data=s.nextInt();
try {
arrayQueue.put(data);
}catch (Exception e){
System.out.println(e.getMessage());
}
break;
case 3:
try {
int data1=arrayQueue.get();
System.out.println("取出数据为"+data1);
}catch (Exception e){
System.out.println(e.getMessage());
}
break;
case 4:
try {
int data1=arrayQueue.getHead();
System.out.println("头数据为"+data1);
}catch (Exception e){
System.out.println(e.getMessage());
}
break;
case 5:
s.close();
loop=false;
System.out.println("退出");
}
}
}
}
class ArrayQueue{
private int max;
private int front;
private int rear;
private int[] arr;
public ArrayQueue(int max) {
this.max = max;
arr=new int[max];
front=-1;
rear=-1;
}
public boolean isFull(){
return rear==max-1;
}
public boolean isEmpty(){
return rear==front;
}
public void put(int n){
if(isFull()){
throw new RuntimeException("队列已满,无法添加");
}
rear++;
arr[rear]=n;
}
public int get(){
if (isEmpty()){
throw new RuntimeException("队列为空,无法取出");
}
int n=arr[++front];
return n;
}
public int getHead(){
if (isEmpty()){
throw new RuntimeException("队列为空,无法取出");
}
int n=arr[front+1];
return n;
}
public void showQueue(){
if (isEmpty()){
throw new RuntimeException("队列为空,无法取出");
}
for (int i = 0; i < arr.length; i++) {
System.out.println("arr["+i+"]="+arr[i]);
}
}
}
环形队列
对前面的数组模拟队列的优化,充分利用数组. 因此将数组看做是一个环形的。(通过取模的方式来实现即可)
思路如下:
- front变量的含义做一个调整:front就指向队列的第一个元素,也就是说arr[front]就是队列的第一个元素front的初始值=0
- rear变量的含义做一个调整:rear指向队列的最后一个元素的后一个位置.因为希望空出一个空间做为约定.rear的初始值=0
- 当队列满时,条件是(rear +1)% maxSize = front【满】
- 对队列为空的条件,rear == front空
- 当我们这样分析,队列中有效的数据的个数(rear + maxSize - front) % maxSize // rear= 1 front=06.我们就可以在原来的队列上修改得到,一个环形队列
package dataStructure;
import java.util.Scanner;
public class CircleArray {
public static void main(String[] args) {
Scanner s=new Scanner(System.in);
boolean loop=true;
ArrayQueue arrayQueue=new ArrayQueue(3);
while (loop){
System.out.println("1.显示队列"+
"\n2.添加数据"+
"\n3.取出数据"+
"\n4.查看头数据"+
"\n5.退出");
int choice=s.nextInt();
switch (choice){
case 1:
arrayQueue.showQueue();
break;
case 2:
int data=s.nextInt();
try {
arrayQueue.put(data);
}catch (Exception e){
System.out.println(e.getMessage());
}
break;
case 3:
try {
int data1=arrayQueue.get();
System.out.println("取出数据为"+data1);
}catch (Exception e){
System.out.println(e.getMessage());
}
break;
case 4:
try {
int data1=arrayQueue.getHead();
System.out.println("头数据为"+data1);
}catch (Exception e){
System.out.println(e.getMessage());
}
break;
case 5:
s.close();
loop=false;
System.out.println("退出");
}
}
}
}
class CircleQueue{
private int max;
private int front;
private int rear;
private int[] arr;
public CircleQueue(int max) {
this.max = max;
arr=new int[max];
}
public boolean isFull(){
return (rear+1)%max==front;
}
public boolean isEmpty(){
return rear==front;
}
public void put(int n){
if(isFull()) {
throw new RuntimeException("队列已满,无法添加");
}
arr[rear]=n;
rear=(rear+1)%max;
}
public int get(){
if (isEmpty()){
throw new RuntimeException("队列为空,无法取出");
}
int n=arr[front];
front=(front+1)%max;
return n;
}
public int getHead(){
if (isEmpty()){
throw new RuntimeException("队列为空,无法取出");
}
int n=arr[front+1];
return n;
}
public void showQueue(){
if (isEmpty()){
throw new RuntimeException("队列为空,无法取出");
}
for (int i = 0; i < front+size(); i++) {
System.out.println("arr["+i+"]="+arr[i]);
}
}
public int size(){
return (rear+max-front)%max;
}
}
单向链表
- 链表是以节点的方式来存储,是链式存储
- 每个节点包含 data 域, next 域:指向下一个节点.
- 链表的各个节点不一定是连续存储.
- 链表分带头节点的链表和没有头节点的链表,根据实际的需求来确定
单链表(带头结点) 逻辑结构示意图如下
添加节点(创建
- 1.先创建一个head头节点,作用就是表示单链表的头
- 2.后面我们每添加一个节点,就直接加入到链表的最后遍历:
- 3.通过一个辅助变量遍历,帮助遍历整个链表
package dataStructure;
public class SingleLinkedList {
public static void main(String[] args) {
Single single=new Single();
Node node1=new Node(1,"aaa");
Node node2=new Node(2,"bbb");
Node node3=new Node(3,"ccc");
single.add(node1);
single.add(node3);
single.add(node2);
single.list();
}
}
class Single{
private Node head=new Node(0,"");
public void add(Node node){
Node temp=head;
while (true){
if(temp.next==null){
break;
}
temp=temp.next;
}
temp.next=node;
}
public void list(){
if(head.next==null){
return;
}
Node temp=head.next;
while (true){
if(temp==null){
break;
}
System.out.println(temp);
temp=temp.next;
}
}
}
class Node{
int no;
String name;
Node next;
public Node(int no, String name) {
this.no = no;
this.name = name;
}
@Override
public String toString() {
return "Node{" +
"no=" + no +
", name='" + name+"}";
}
}
顺序添加
- 1.首先找到新添加的节点的位置,是通过辅助变量(指针),通过遍历来搞定
- 2.新的节点.next=temp.next
- 3.将temp.next=新的节点
package dataStructure;
class Single{
private Node head=new Node(0,"");
public void addByOrder(Node node){
Node temp=head;
boolean flag=false;
while (true){
if(temp.next==null){
break;
}
if(temp.next.no>node.no){
break;
}else if(temp.next.no== node.no) {
flag=true;
break;
}
temp=temp.next;
}
if (flag){
System.out.println(node.no+"节点已经存在");
}else {
node.next=temp.next;
temp.next=node;
}
}
}
修改节点
- 1.先找到该节点,通过遍历
- 2.temp.name=Node.name (修改节点名字)
package dataStructure;
class Single{
private Node head=new Node(0,"");
public void update(Node node){
if(head.next==null){
return;
}
Node temp=head.next;
boolean flag=false;
while (true){
if(temp==null){
break;
}
if (temp.no== node.no){
flag=true;
break;
}
temp=temp.next;
}
if(flag){
temp.name=node.name;
}else {
System.out.println("没有找到"+node.no+"节点");
}
}
}
删除节点
- 1.我们先找到需要删除的这个节点的前一个节点temp
- 2.temn.next =temp.next.next
- 3.被删除的节点,将不会有其它引用指向,会被垃圾回收机制回收
package dataStructure;
class Single{
private Node head=new Node(0,"");
public void del(Node node){
Node temp=head;
boolean flag=false;
while(true){
if(temp.next==null){
break;
}
if(temp.next.no==node.no){
flag=true;
break;
}
temp=temp.next;
}
if (flag){
temp.next=temp.next.next;
}else {
System.out.println(node.no+"节点不存在");
}
}
}
获取有效节点个数
public static int getLength(Node head){
if(head.next==null){
return 0;
}
Node cur=head.next;
int length=0;
while (cur!=null){
length++;
cur=cur.next;
}
return length;
}
获取倒数第index个节点
- 1.编写一个方法,接收head节点,同时接收一个index
- 2.index表示是倒数第index个节点
- 3.先把链表从头到尾遍历,得到链表的总的长度getLength
- 4.得到size后,我们从链表的第一个开始遍历(size-index)个,就可以得到
- 5.如果找到了,则返回该节点,否则返回nul
public Node findLastIndexNode(Node head,int index){
if (head.next==null){
return null;
}
int size=getLength(head);
if (index<0||index>size){
return null;
}
Node cur=head.next;
for (int i = 0; i < size-index; i++) {
cur=cur.next;
}
return cur;
}
反转单链表
- 1.先定义一个节点 reverseHead =new HeroNode();
- 2.从头到尾遍历原来的链表,每遍历一个节点,就将其取出,并放在新的链表reverseHead的最前端
- 3.原来的链表的head.next =reverseHead.next
public static void reverseList(Node head){
if (head.next==null||head.next.next==null){
return;
}
Node cur=head.next;
Node next=null;
Node reverse=new Node(0,"");
while (cur!=null){
next=cur.next;
cur.next=reverse.next;
reverse.next=cur;
cur=next;
}
head.next=reverse.next;
}
双向链表
单向链表缺点
- 1)单向链表,查找的方向只能是一个方向,而双向链表可以向前或者向后查找。
- 2)单向链表不能自我删除,需要靠辅助节点﹐而双向链表,则可以自我删除,所以前面我们单链表删除时节点,总是找到temp,temp是待删除节点的前一个节点
遍历
- (1)遍历方和单链表一样,可以向前,也可以向后查找
添加(默认添加到双向链表的最后)
- (1)先找到双向链表的最后这个节点
- (2) temp.next= newHeroNode
- (3) newHeroNode.pre = temp;
修改(思路和单向链表一样) 删除
- (1)直接找到要删除的这个节点,比如 temp
- (2)temp.pre.next =temp.next;
- (3) temp.next.pre =temp.pre;
package dataStructure;
public class DoubleLinkedList {
public static void main(String[] args) {
Double aDouble = new Double();
Node1 node1=new Node1(1,"aaa");
Node1 node2=new Node1(2,"bbb");
Node1 node3=new Node1(3,"ccc");
aDouble.add(node1);
aDouble.add(node2);
aDouble.add(node3);
aDouble.del(node2);
aDouble.list();
}
}
class Double{
private Node1 head=new Node1(0,"");
public Node1 getHead() {
return head;
}
public void add(Node1 node){
Node1 temp=head;
while (true){
if(temp.next==null){
break;
}
temp=temp.next;
}
temp.next=node;
node.pre=temp;
}
public void update(Node1 node){
if(head.next==null){
return;
}
Node1 temp=head.next;
boolean flag=false;
while (true){
if(temp==null){
break;
}
if (temp.no== node.no){
flag=true;
break;
}
temp=temp.next;
}
if(flag){
temp.name=node.name;
}else {
System.out.println("没有找到"+node.no+"节点");
}
}
public void del(Node1 node){
Node1 temp=head.next;
boolean flag=false;
while(true){
if(temp==null){
break;
}
if(temp.no==node.no){
flag=true;
break;
}
temp=temp.next;
}
if (flag){
node.pre.next=node.next;
if (temp.next!=null){
node.next.pre=node.pre;
}
}else {
System.out.println(node.no+"节点不存在");
}
}
public void list(){
if(head.next==null){
return;
}
Node1 temp=head.next;
while (true){
if(temp==null){
break;
}
System.out.println(temp);
temp=temp.next;
}
}
}
class Node1{
int no;
String name;
Node1 next;
Node1 pre;
public Node1(int no, String name) {
this.no = no;
this.name = name;
}
@Override
public String toString() {
return "Node{" +
"no=" + no +
", name='" + name+"}";
}
}
环形单向链表
构建
- 1.先创建第一个节点,让first指向该节点,并形成环形
- 2.后面当我们每创建一个新的节点,就把该节点,加入到已有的环形链表中即可.
遍历环形链表
- 1.先让一个铺助指针(变量)curBoy,指向first节点
- 2.然后通过一个while循环遍历该环形链表即可 curBoy.next == first结束
package dataStructure;
public class Josepfu {
public static void main(String[] args) {
CircleSingeLinkedList circleSingeLinkedList=new CircleSingeLinkedList();
circleSingeLinkedList.addBoy(5);
circleSingeLinkedList.list();
}
}
class CircleSingeLinkedList{
Boy first=new Boy(-1);
public void addBoy(int num){
if (num<1){
return;
}
Boy curBoy=first;
for (int i = 0; i <= num; i++) {
Boy boy=new Boy(i);
if (i==1){
first=boy;
first.setNext(first);
curBoy=first;
}else {
curBoy.setNext(boy);
boy.setNext(first);
curBoy=boy;
}
}
}
public void list(){
if(first==null){
System.out.println("环形链表为空");
}
Boy curBoy=first;
while (true){
System.out.println(curBoy.getNo());
if(curBoy.getNext()==first){
break;
}
curBoy=curBoy.getNext();
}
}
}
class Boy{
private int no;
private Boy next;
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;
}
}
约瑟夫问题
Josephu问题为:设编号为1,2, n的n个人围坐一圈,约定编号为k (1<=k=n)的人从1开始报数,数到m的那个人出列,它的下一位又从1开始报数,数到m的那个人又出列,依次类推,直到所有人出列为止,由此产生一个出队编号的序列。
n=5,有5个人 k=1,从第一个人开始报数 m =2,数2下
出圈思路
- 1.需求创建一个辅助指针(变量) helper ,事先应该指向环形链表的最后这个节点.
- 2.报数前,先让first和 helper移动k-1次
- 3.当报数时,让first和helper指针同时的移动m-1次这时就可以将first指向的节点出圈
first = first.next helper.next = first
原来first指向的节点就没有任何引用,就会被回收
package dataStructure;
public class Josepfu {
public static void main(String[] args) {
CircleSingeLinkedList circleSingeLinkedList=new CircleSingeLinkedList();
circleSingeLinkedList.countBoy(1,2,5);
}
}
class CircleSingeLinkedList{
Boy first=new Boy(-1);
public void countBoy(int startNo,int countNum,int num){
if(first==null||startNo>num||startNo<1){
return;
}
Boy helper=first;
while (helper.getNext() != first) {
helper = helper.getNext();
}
for (int i = 0; i < startNo-1; i++) {
first=first.getNext();
helper=helper.getNext();
}
while (first != helper) {
for (int i = 0; i < countNum - 1; i++) {
first = first.getNext();
helper = helper.getNext();
}
System.out.println(first.getNo() + "号出圈");
first = first.getNext();
helper.setNext(first);
}
System.out.println(first.getNo()+"最后在圈中");
}
}
class Boy{
private int no;
private Boy next;
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;
}
}
栈
- 1)栈的英文为(stack)
- 2)栈是一个先入后出(FILO-First In Last Out)的有序列表。
- 3)栈是限制线性表中元素的插入和删除只能在线性表的同一端进行的一种特殊线性表。允许插入和删除的一端,为变化的一端,称为栈顶(Top),另一端为固定的一端,称为栈底(Bottom)。
- 4 )根据栈的定义可知,最先放入栈中元素在栈底,最后放入的元素在栈顶,而删除元素刚好相反,最后放入的元素最先删除,最先放入的元素最后删除
栈的实现思路
- 1.定义一个top来表示柱顶,初始化-1
- 2.入栈的操作,当有数据加入到栈时,top++; stack[top] = data;
- 3.出栈的操作,int value = stack[top]; top–
package dataStructure;
import java.util.Scanner;
public class Stack {
public static void main(String[] args) {
ArrayStack arrayStack=new ArrayStack(5);
boolean flag=true;
while (flag) {
System.out.println("----------菜单----------");
System.out.println("1.放入数据\n"+
"2.取出数据\n"+
"3.显示全部\n"+
"4.退出\n"+
"请选择:");
Scanner scanner=new Scanner(System.in);
int choice = scanner.nextInt();
switch (choice) {
case (1):
System.out.println("输入数据:");
int num = scanner.nextInt();
arrayStack.push(num);
break;
case (2):
try {
arrayStack.pop();
break;
} catch (Exception e) {
e.getMessage();
}
case (3):
arrayStack.show();
break;
case (4):
scanner.close();
flag = false;
break;
}
}
}
}
class ArrayStack{
int max;
int top=-1;
int[] stack;
public ArrayStack(int max) {
this.max = max;
stack=new int[this.max];
}
public boolean isFull(){
return top==max-1;
}
public boolean isEmpty(){
return top==-1;
}
public void push(int num){
if (isFull()){
System.out.println("栈满");
return;
}
top++;
stack[top]=num;
}
public int pop(){
if (isEmpty()){
throw new RuntimeException("栈空");
}
int num=stack[top];
top--;
System.out.println("取出数据:"+num);
return num;
}
public void show(){
if (isEmpty()){
System.out.println("栈空");
return;
}
for (int i = top; i >=0 ; i--) {
System.out.println("数据"+stack[i]);
}
}
}
综合计算器
计算思路
- 1.通过一个index值(索引),来遍历我们的表达式
- 2.如果发现是一个数字,直接入数栈
- 3.如果发现扫描到是一个符号,就分如下情况
- 3.1如果发现当前的符号栈为空,就直接入栈
- 3.2如果符号栈有操作符,就进行比较,如果当前的操作符的优先级小于或者等于截中的操作符,就需要从数栈中pop出两个数,在从符号栈中pop出一个符号,进行运算,将得到结果,入数栈,然后将当前的操作符入符号栈,如果当前的操作符的优先级大于栈中的操作符,就直接入符号栈.
- 4.当表达式扫描完毕,就顺序的从数栈和符号栈中pop出相应的数和符号,并运行.
- 5.最后在数栈只有一个数字,就是表达式的结果
package dataStructure;
public class Calculator {
public static void main(String[] args) {
String formula="7*4+1-3";
ArrayStack2 num=new ArrayStack2(10);
ArrayStack2 symbol=new ArrayStack2(10);
int index=0;
int sum=0;
int num1=0;
int num2=0;
int oper=0;
char ch=' ';
StringBuilder keepNum= new StringBuilder();
while (index<=formula.length()-1){
ch=formula.substring(index,index+1).charAt(0);
if(symbol.isSymbol(ch)){
if(!symbol.isEmpty()){
if(symbol.priority(ch)<=symbol.top){
num1=num.pop();
num2=num.pop();
oper=symbol.pop();
sum=symbol.sum(num1,num2,oper);
num.push(sum);
symbol.push(ch);
}else {
symbol.push(ch);
}
}else {
symbol.push(ch);
}
}else {
keepNum.append(ch);
if(index==formula.length()-1){
num.push(Integer.parseInt(keepNum.toString()));
}
else {
if(symbol.isSymbol(formula.substring(index+1,index+2).charAt(0))){
num.push(Integer.parseInt(keepNum.toString()));
keepNum = new StringBuilder();
}
}
}
index++;
}
while (!symbol.isEmpty()){
num1=num.pop();
num2=num.pop();
oper=symbol.pop();
sum=num.sum(num1,num2,oper);
num.push(sum);
}
int res=num.pop();
System.out.println("结果为:"+res);
}
}
class ArrayStack2{
int max;
int top=-1;
int[] stack;
public ArrayStack2(int max) {
this.max = max;
stack=new int[this.max];
}
public boolean isFull(){
return top==max-1;
}
public boolean isEmpty(){
return top==-1;
}
public int getTop(){
return stack[top];
}
public void push(int num){
if (isFull()){
System.out.println("栈满");
return;
}
top++;
stack[top]=num;
}
public int pop(){
if (isEmpty()){
throw new RuntimeException("栈空");
}
int num=stack[top];
top--;
System.out.println("取出数据:"+num);
return num;
}
public void show(){
if (isEmpty()){
System.out.println("栈空");
return;
}
for (int i = top; i >=0 ; i--) {
System.out.println("数据"+stack[i]);
}
}
public int priority(int symbol){
if(symbol=='*'||symbol=='/'){
return 1;
}
else if(symbol=='+'||symbol=='-'){
return 0;
}else {
return -1;
}
}
public int sum(int num1,int num2,int symbol){
int sum=0;
if(symbol=='*'){
sum=num1*num2;
}else if(symbol=='/'){
sum=num2/num1;
}else if(symbol=='+'){
sum=num2+num1;
}else if(symbol=='-'){
sum=num2-num1;
}
return sum;
}
public boolean isSymbol(int symbol){
return symbol=='*'||symbol=='/'||symbol=='+'||symbol=='-';
}
}
后缀表达式(逆波兰表达式)
思路分析: 例:(7+1)-3*6对应的后缀表达式就是7 1 + 3 6 * -
- 1.从左至右扫描,将7和1压入堆栈;
- 2.遇到+运算符,因此弹出7和1(1为栈顶元素,7为次顶元素),计算出7+1的值,得8,再将8入栈;
- 3.将3,6入栈;
- 4.接下来是运算符,因此弹出3和6,计算出36=18,将18入栈;
- 5.最后是-运算符,计算出10-18的值,即-10,由此得出最终结果
package dataStructure;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Stack;
public class PostfixExpression {
public static void main(String[] args) {
String suffixExpression = "7 1 + 3 6 * -";
List<String> list = getList(suffixExpression);
int calculate = calculate(list);
System.out.println(calculate);
}
public static List<String> getList(String suffixExpression) {
String[] s = suffixExpression.split(" ");
return new ArrayList<>(Arrays.asList(s));
}
public static int calculate(List<String> list) {
Stack<String> stack = new Stack<>();
for (String num : list) {
if (num.matches("\\d")) {
stack.push(num);
} else {
int num2 = Integer.parseInt(stack.pop());
int num1 = Integer.parseInt(stack.pop());
int res = sum(num1, num2, num);
stack.push(""+res);
}
}
return Integer.parseInt(stack.pop());
}
public static int sum ( int num1, int num2, String symbol){
int res = 0;
switch (symbol) {
case "+":
res = num1 + num2;
break;
case "-":
res = num1 - num2;
break;
case "*":
res = num1 * num2;
break;
case "/":
res = num1 / num2;
}
return res;
}
}
中缀转后缀表达式
思想路线
public static List<String> parseSuffixExpression(List<String> list){
Stack<String> s1=new Stack<>();
List<String> s2=new ArrayList<>();
for (String item:list){
if (item.matches("\\d")) {
s2.add(item);
}else if(item.equals("(")){
s1.push(item);
}else if(item.equals(")")){
while (!s1.peek().equals("(")){
s2.add(s1.pop());
}
s1.pop();
}else {
while (s1.size()!=0&&priority(item)<=priority(s1.peek())){
s2.add(s1.pop());
}
s1.push(item);
}
}
while (s1.size()!=0){
s2.add(s1.pop());
}
return s2;
}
public static List<String> getExpressionList(String suffixExpression){
List<String> list=new ArrayList<>();
int index = 0;
char ch;
String str;
do {
if((ch=suffixExpression.charAt(index))<48||(ch=suffixExpression.charAt(index))>57){
list.add(String.valueOf(ch));
index++;
}else {
str="";
while (index<suffixExpression.length()&&(ch=suffixExpression.charAt(index))>=48&&(ch=suffixExpression.charAt(index))<=57){
str+=ch;
index++;
}
list.add(str);
}
}while (index<suffixExpression.length());
return list;
}
完整版
package dataStructure;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Stack;
public class PostfixExpression {
public static void main(String[] args) {
String suffixExpression = "(7+1)-3*6";
List<String> list = getExpressionList(suffixExpression);
System.out.println(list);
List<String> list1 = parseSuffixExpression(list);
System.out.println(list1);
int calculate = calculate(list1);
System.out.println(calculate);
}
public static List<String> parseSuffixExpression(List<String> list){
Stack<String> s1=new Stack<>();
List<String> s2=new ArrayList<>();
for (String item:list){
if (item.matches("\\d")) {
s2.add(item);
}else if(item.equals("(")){
s1.push(item);
}else if(item.equals(")")){
while (!s1.peek().equals("(")){
s2.add(s1.pop());
}
s1.pop();
}else {
while (s1.size()!=0&&priority(item)<=priority(s1.peek())){
s2.add(s1.pop());
}
s1.push(item);
}
}
while (s1.size()!=0){
s2.add(s1.pop());
}
return s2;
}
public static List<String> getExpressionList(String suffixExpression){
List<String> list=new ArrayList<>();
int index = 0;
char ch;
String str;
do {
if((ch=suffixExpression.charAt(index))<48||(ch=suffixExpression.charAt(index))>57){
list.add(String.valueOf(ch));
index++;
}else {
str="";
while (index<suffixExpression.length()&&(ch=suffixExpression.charAt(index))>=48&&(ch=suffixExpression.charAt(index))<=57){
str+=ch;
index++;
}
list.add(str);
}
}while (index<suffixExpression.length());
return list;
}
public static List<String> getList(String suffixExpression) {
String[] s = suffixExpression.split(" ");
return new ArrayList<>(Arrays.asList(s));
}
public static int calculate(List<String> list) {
Stack<String> stack = new Stack<>();
for (String num : list) {
if (num.matches("\\d")) {
stack.push(num);
} else {
int num2 = Integer.parseInt(stack.pop());
int num1 = Integer.parseInt(stack.pop());
int res = sum(num1, num2, num);
stack.push(""+res);
}
}
return Integer.parseInt(stack.pop());
}
public static int priority(String symbol){
int res=0;
switch(symbol){
case "+":
case "-":
res=1;
break;
case "*":
case "/":
res=2;
break;
}
return res;
}
public static int sum ( int num1, int num2, String symbol){
int res = 0;
switch (symbol) {
case "+":
res = num1 + num2;
break;
case "-":
res = num1 - num2;
break;
case "*":
res = num1 * num2;
break;
case "/":
res = num1 / num2;
}
return res;
}
}
哈希表
散列表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。
package dataStructure;
import java.util.Objects;
import java.util.Scanner;
public class HashTable {
public static void main(String[] args) {
HashTab hashTab=new HashTab(7);
boolean flag=true;
while (flag) {
System.out.println("----------菜单----------");
System.out.println("1.添加数据\n"+
"2.查找数据\n"+
"3.删除数据\n"+
"4.显示全部\n"+
"5.退出\n"+
"请选择:");
Scanner scanner=new Scanner(System.in);
int choice = scanner.nextInt();
switch (choice) {
case (1):
System.out.println("输入id和name:");
int id = scanner.nextInt();
String name=scanner.next();
Emp emp=new Emp(id,name);
hashTab.add(emp);
break;
case (2):
System.out.println("输入id:");
int id1 = scanner.nextInt();
hashTab.findById(id1);
break;
case (3):
System.out.println("输入id:");
int id2 = scanner.nextInt();
hashTab.delete(id2);
break;
case (4):
hashTab.list();
case (5):
scanner.close();
flag = false;
break;
}
}
}
}
class HashTab{
private final int size;
private final LinkListed[] empHashTable;
public HashTab(int size){
this.size=size;
empHashTable=new LinkListed[size];
for (int i = 0; i < size; i++) {
empHashTable[i]=new LinkListed();
}
}
public void add(Emp emp){
int empHashTableIndex=hashFun(emp.id);
empHashTable[empHashTableIndex].add(emp);
}
public void list(){
for (int i = 0; i < size; i++) {
empHashTable[i].list(i);
}
}
public void findById(int id){
int empHashTableIndex=hashFun(id);
Emp emp=empHashTable[empHashTableIndex].findById(id);
if (emp!=null){
System.out.println(id+"数据在"+"第"+empHashTableIndex+"链表上");
}else {
System.out.println("没有该数据");
}
}
public void delete(int id){
int empHashTableIndex=hashFun(id);
empHashTable[empHashTableIndex].delete(id);
}
public int hashFun(int id){
return id%size;
}
}
class Emp{
int id;
String name;
Emp next;
public Emp(int id, String name) {
this.id = id;
this.name = name;
}
}
class LinkListed{
private Emp head;
public void add(Emp emp){
if (head==null){
head=emp;
}
Emp curEmp=head;
while (curEmp.next!=null){
curEmp=curEmp.next;
}
curEmp.next=emp;
}
public void list(int no){
if (head==null){
System.out.println("第"+(no+1)+"行没有元素");
return;
}
System.out.println("第"+(no+1)+"行数据:");
Emp curEmp=head;
while (true) {
System.out.println("id:" + curEmp.id + "name:" + curEmp.name);
if (curEmp.next == null){
break;
}
curEmp = curEmp.next;
}
System.out.println();
}
public Emp findById(int id){
if (head==null){
System.out.println("没有数据");
}
Emp curEmp=head;
while (true){
if (curEmp.id==id){
break;
}
if (curEmp.next == null) {
curEmp=null;
break;
}
curEmp=curEmp.next;
}
return curEmp;
}
public void delete(int id){
if (head==null){
System.out.println("没有数据");
}
Emp curEmp=head;
while (true){
if (curEmp.next==null){
break;
}
if (curEmp.next.id==id){
curEmp.next=null;
break;
}
curEmp=curEmp.next;
}
}
}
树
树的存储方式 能提高数据存储,读取的效率,比如利用二叉排序树(Binary Sort Tree),既可以保证数据的检索速度,同时也可以保证数据的插入,删除,修改的速度。
树的常用术语
- 1)节点
- 2)根节点
- )父节点
- 4)子节点
- 5)叶子节点(没有子节点的节点)
- 6)节点的权(节点值)
- 7)路径(从root节点找到该节点的路线)
- 8)层
- 9)子树
- 10)树的高度(最大层数)
- 11)森林:多颗子树构成森林
二叉树
(1) 树有很多种,每个节点最多只能有两个子节点的一种形式称为二叉树。 (2) 如果该二叉树的所有叶子节点都在最后一层,并且结点总数=2^n-1 , n为层数,则我们称为满二叉树。 (3)
如果该二叉树的所有叶子节点都在最后一层或者倒数第二层,而且最后一层的叶子节点在左边连续,倒数第二层的叶子节点在右边连续,我们称为完全二叉树
1)前序遍历:先输出父节点,再遍历左子树和右子树 2)中序遍历:先遍历左子树,再输出父节点,再遍历右子树 3)后序遍历:先遍历左子树,再遍历右子树,最后输出父节点 4)小结:看输出父节点的顺序,就确定是前序,中序还是后序
二叉树的前序,中序,后序的遍历步骤
- 1.创建一颗二叉树
- 2.前序遍历(1,2,4,5,3,6)
2.1先输出当前节点(初始的时候是root节点) 2.2如果左子节点不为空,则递归继续前序遍历 2.3如果右子节点不为空,则递归继续前序遍历 - 3.中序遍历(4,2,5,1,6,3)
3.1如果当前节点的左子节点不为空,则递归中序遍历 3.2输出当前节点 3.2如果当前节点的右子节点不为空,则递归中序遍历 - 4.后序遍历(4,5,2,6,3,1)
4.1如果当前节点的左子节点不为空,则递归后序遍历 4.2如果当前节点的右子节点不为空,则递归后序遍历 4.3输出当前节点
package dataStructure;
public class BinaryTree {
public static void main(String[] args) {
TreeNode root=new TreeNode(1,"1");
TreeNode node2=new TreeNode(2,"2");
TreeNode node3=new TreeNode(3,"3");
TreeNode node4=new TreeNode(4,"4");
TreeNode node5=new TreeNode(5,"5");
TreeNode node6=new TreeNode(6,"6");
Tree tree=new Tree(root);
root.left=node2;
root.right=node3;
node2.left=node4;
node2.right=node5;
node3.left=node6;
}
}
class Tree{
private final TreeNode root;
public Tree(TreeNode root) {
this.root = root;
}
public void perOrder(){
System.out.println(this);
if (root!=null){
root.perOrder();
}else {
System.out.println("没有数据");
}
}
public void infixOrder(){
System.out.println(this);
if (root!=null){
root.infixOrder();
}else {
System.out.println("没有数据");
}
}
public void postOrder(){
System.out.println(this);
if (root!=null){
root.postOrder();
}else {
System.out.println("没有数据");
}
}
}
class TreeNode{
int no;
String name;
TreeNode left;
TreeNode right;
public TreeNode(int no, String name) {
this.no = no;
this.name = name;
}
@Override
public String toString() {
return "TreeNode{" +
"no=" + no +
", name=" + name +
'}';
}
public void perOrder(){
System.out.println(this);
if (this.left!=null){
this.left.perOrder();
}
if (this.right!=null){
this.right.perOrder();
}
}
public void infixOrder(){
if (this.left!=null){
this.left.infixOrder();
}
System.out.println(this);
if (this.right!=null){
this.right.infixOrder();
}
}
public void postOrder(){
if (this.left!=null){
this.left.postOrder();
}
if (this.right!=null){
this.right.postOrder();
}
System.out.println(this);
}
}
二叉树前序,中序,后序查找 前序查找思路
- 1.先判断当前结点的no是否等于要查找的
- 2.如果是相等,则返回当前结点
- 3.如果不等,则判断当前结点的左子节点是否为空,如果不为空,则递归前序查找
- 4.如果左递归前序查找,找到结点,则返回,否继续判断,当前的结点的右子节点是否为空,如果不空,则继续向右递归前序查找.
中序查找思路
- 1.判断当前结点的左子节点是否为空,如果不为空,则递归中序查找
- 2如果找到,则返回,如果没有找到,就和当前结点比较,如果是则返回当前结点,否则继续进行右递归的中序查找
- 3.如果右递归中序查找,找到就返回,否则返回null
后序查找思路
- 1.判断当前结点的左子节点是否为空,如果不为空,则递归后序查找
- 2如果找到,就返回,如果没有找到,就判断当前结点的右子节点是否为空,如果不为空,则右递归进行后序查找,如果找到,就返回
- 3.就和当前结点进行,比如,如果是则返回,否则返回null
package dataStructure;
public class BinaryTree {
public static void main(String[] args) {
TreeNode root=new TreeNode(1,"1");
TreeNode node2=new TreeNode(2,"2");
TreeNode node3=new TreeNode(3,"3");
TreeNode node4=new TreeNode(4,"4");
TreeNode node5=new TreeNode(5,"5");
TreeNode node6=new TreeNode(6,"6");
Tree tree=new Tree(root);
root.left=node2;
root.right=node3;
node2.left=node4;
node2.right=node5;
node3.left=node6;
}
}
class Tree{
private final TreeNode root;
public Tree(TreeNode root) {
this.root = root;
}
public void perOrderSearch(int no){
if (root!=null){
TreeNode node = root.perOrderSearch(no);
System.out.println(node);
}
}
public void infixOrderSearch(int no){
if (root!=null){
TreeNode node = root.infixOrderSearch(no);
System.out.println(node);
}
}
public void postOrderSearch(int no){
if (root!=null){
TreeNode node = root.postOrderSearch(no);
System.out.println(node);
}
}
}
class TreeNode{
int no;
String name;
TreeNode left;
TreeNode right;
public TreeNode(int no, String name) {
this.no = no;
this.name = name;
}
@Override
public String toString() {
return "TreeNode{" +
"no=" + no +
", name=" + name +
'}';
}
public TreeNode perOrderSearch(int no){
if (this.no==no){
return this;
}
TreeNode node = null;
if (this.left!=null){
node=this.left.perOrderSearch(no);
}
if (node!=null){
return node;
}
if (this.right!=null){
node=this.right.perOrderSearch(no);
}
return node;
}
public TreeNode infixOrderSearch(int no){
TreeNode node = null;
if (this.left!=null){
node=this.left.infixOrderSearch(no);
}
if (node!=null){
return node;
}
if (this.no==no){
return this;
}
if (this.right!=null){
node=this.right.infixOrderSearch(no);
}
return node;
}
public TreeNode postOrderSearch(int no){
TreeNode node = null;
if (this.left!=null){
node=this.left.postOrderSearch(no);
}
if (node!=null){
return node;
}
if (this.right!=null){
node=this.right.postOrderSearch(no);
}
if (node!=null){
return node;
}
if (this.no==no){
return this;
}
return node;
}
}
删除节点 删除结点的规定:
- 1)如果删除的节点是叶子节点,则删除该节点
- 2)如果删除的节点是非叶子节点,则删除该子树
思路
- 考虑如果树是空树root ,如果只有一个root结点,则等价将二叉树置空
- 1.因为我们的二叉树是单向的,所以我们是判断当前结点的子结点是转需要删除结点,而不能去判断当前这个结点是不是需要删除结点.
- 2如果当前结点的左子结点不为空,并且左子结点就是要删除结点,就将this.left=null;并且就返回(结束递归删除)
- 3.如果当前结点的右子结点不为空,并且右子结点就是要删除结点,就将this.right=null;并且就返回(结束递归删除)
- 4.如果第2和第3步没有删除结点,那么我们就需要向左子树进行递归删除
- 5.如果第4步也没有删除结点,则应当向右子树进行递归删除.
package dataStructure;
public class BinaryTree {
public static void main(String[] args) {
TreeNode root=new TreeNode(1,"1");
TreeNode node2=new TreeNode(2,"2");
TreeNode node3=new TreeNode(3,"3");
TreeNode node4=new TreeNode(4,"4");
TreeNode node5=new TreeNode(5,"5");
TreeNode node6=new TreeNode(6,"6");
Tree tree=new Tree(root);
root.left=node2;
root.right=node3;
node2.left=node4;
node2.right=node5;
node3.left=node6;
tree.delete(5);
tree.perOrder();
}
}
class Tree{
private TreeNode root;
public Tree(TreeNode root) {
this.root = root;
}
public void delete(int no){
if (root!=null){
if (root.no==no){
root=null;
}else {
root.delete(no);
}
}else {
System.out.println("没有数据");
}
}
}
class TreeNode{
int no;
String name;
TreeNode left;
TreeNode right;
public TreeNode(int no, String name) {
this.no = no;
this.name = name;
}
@Override
public String toString() {
return "TreeNode{" +
"no=" + no +
", name=" + name +
'}';
}
public void delete(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.delete(no);
}
if (this.right!=null){
this.right.delete(no);
}
}
}
顺序存储二叉树
基本说明 从数据存储来看,数组存储方式和树的存储方式可以相互转换,即数组可以转换成树,树也可以转换成数组,
顺序存储二叉树的特点:
- 1)顺序二叉树通常只考虑完全二叉树
- 2)第n个元素的左子节点为2*n+1
- 3)第n个元素的右子节点为2*n+2
- 4)第n个元素的父节点为(n-1)/ 2
- 5)n :表示二叉树中的第几个元素(按0开始编号如图所示)
package dataStructure;
public class ArrBinaryTree {
public static void main(String[] args) {
int[] arr={1,2,3,4,5};
ArrTree tree=new ArrTree(arr);
tree.perOrder();
tree.infixOrder();
tree.postOrder();
}
}
class ArrTree{
private int[] arr;
public ArrTree(int[] arr) {
this.arr = arr;
}
public void perOrder(){
this.perOrder(0);
}
public void infixOrder(){
this.infixOrder(0);
}
public void postOrder(){
this.postOrder(0);
}
public void perOrder(int index){
if (arr == null) {
assert false;
if (arr.length<=0) {
System.out.println("数组为空");
}
}
System.out.println(arr[index]);
if (2*index+1< arr.length) {
perOrder(2*index+1);
}
if (2*index+2<arr.length){
perOrder(2*index+2);
}
}
public void infixOrder(int index){
if (arr == null) {
assert false;
if (arr.length<=0) {
System.out.println("数组为空");
}
}
if (2*index+1< arr.length) {
infixOrder(2*index+1);
}
System.out.println(arr[index]);
if (2*index+2<arr.length){
infixOrder(2*index+2);
}
}
public void postOrder(int index){
if (arr == null) {
assert false;
if (arr.length<=0) {
System.out.println("数组为空");
}
}
if (2*index+1< arr.length) {
postOrder(2*index+1);
}
if (2*index+2<arr.length){
postOrder(2*index+2);
}
System.out.println(arr[index]);
}
}
线索化二叉树
充分的利用各个节点的左右指针,让各个节点可以指向自己的前后节点-线索二叉树 基本概念
- 1)n个结点的二叉链表中含有n+l【公式2n-(n-1)=n+1】个空指针域。利用二叉链表中的空指针域,存放指向该结点在某种遍历次序下的前驱和后继结点的指针(这种附加的指针称为"线索")
- 2)这种加上了线索的二叉链表称为线索链表,相应的二叉树称为线索二叉树(Threaded BinaryTree)。根据线索性质的不同,线索二叉树可分为前序线索二叉树、中序线索二叉树和后序线索二叉树三种
- 3)一个结点的前一个结点,称为前驱结点
- 4)一个结点的后一个结点,称为后继结点
说明:当线索化二叉树后,Node节点的属性 left和 right ,有如下情况:
- l1) left 指向的是左子树,也可能是指向的前驱节点.比如①节点left 指向的左子树,而⑩节点的 left 指向的就是前驱节点.
- 2)right 指向的是右子树,也可能是指向后继节点,比如①节点right 指向的是右子树,而⑩节点的 right 指向的是后继节点.
package dataStructure.BinaryTree;
public class ThreadedBinaryTree {
public static void main(String[] args) {
Node root=new Node(1,"1");
Node node2=new Node(2,"2");
Node node3=new Node(3,"3");
Node node4=new Node(4,"4");
Node node5=new Node(5,"5");
Node node6=new Node(6,"6");
ThreadedTree tree=new ThreadedTree(root);
root.left=node2;
root.right=node3;
node2.left=node4;
node2.right=node5;
node3.left=node6;
tree.threadedNode();
tree.ThreadedList();
}
}
class ThreadedTree {
private final Node root;
private Node pre;
public ThreadedTree(Node root) {
this.root = root;
}
public void threadedNode(){
this.threadedNode(root);
}
public void threadedNode(Node node){
if (node==null){
return;
}
threadedNode(node.left);
if (node.left==null){
node.left=pre;
node.leftType=1;
}
if (pre!=null&&pre.right==null){
pre.right=node;
pre.rightType=1;
}
pre=node;
threadedNode(node.right);
}
public void ThreadedList(){
Node node=root;
while (node!=null){
while (node.leftType == 0) {
node=node.left;
}
System.out.println(node);
while (node.rightType==1){
node=node.right;
System.out.println(node);
}
node=node.right;
}
}
}
class Node {
int no;
String name;
Node left;
Node right;
int leftType;
int rightType;
public Node(int no, String name) {
this.no = no;
this.name = name;
}
@Override
public String toString() {
return "Node{" +
"no=" + no +
", name='" + name + "}";
}
}
哈弗曼树
基本介绍
- 1)给定n个权值作为n个叶子结点,构造一棵二叉树,若该树的带权路径长度(wpl)达到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树(HuffmanTree),还有的书翻译为霍夫曼树。
- 2)赫夫曼树是带权路径长度最短的树,权值较大的结点离根较近
赫夫曼树几个重要概念
- 1)路径和路径长度:在一棵树中,从一个结点往下可以达到的孩子或孙子结点之间的通路,称为路径。通路中分支的数目称为路径长度。若规定根结点的层数为1,则从根结点到第L层结点的路径长度为L-1
- 2)结点的权及带权路径长度:若将树中结点赋给一个有着某种含义的数值,则这个数值称为该结点的权。结点的带权路径长度为:从根结点到该结点之间的路径长度与该结点的权的乘积
- 3)树的带权路径长度:树的带权路径长度规定为所有叶子结点的带权路径长度之和,记为WPL(weighted pathlength),权值越大的结点离根结点越近的二叉树才是最优二叉树。
- 4)WPL最小的就是赫夫曼树
构成赫夫曼树的步骤:
{13,7,8,3,29,6,1}
- 1)从小到大进行排序,将每一个数据,每个数据都是一个节点﹐每个节点可以看成是一颗最简单的二叉树
- 2)取出根节点权值最小的两颗二叉树
- 3)组成一颗新的二叉树,该新的二叉树的根节点的权值是前面两颗二叉树根节点权值的和
- 4)再将这颗新的二叉树,以根节点的权值大小再次排序,不断重复1-2-3-4的步骤,直到数列中,所有的数据都被处理,就得到一颗赫夫曼树
package dataStructure.Tree;
import java.util.ArrayList;
import java.util.Collections;
public class HuffmanTree {
public static void main(String[] args) {
int[] arr={13,7,8,3,29,6,1};
HuffmanNode huffmanNode = creatHuffmanTree(arr);
perOrder(huffmanNode);
}
public static void perOrder(HuffmanNode root){
if (root!=null){
root.perOrder();
}else {
System.out.println("数据为空");
}
}
public static HuffmanNode creatHuffmanTree(int[] arr){
ArrayList<HuffmanNode> list=new ArrayList<>();
for (int value:arr){
list.add(new HuffmanNode(value));
}
while (list.size()>1){
Collections.sort(list);
System.out.println(list);
HuffmanNode leftNode = list.get(0);
HuffmanNode rightNode = list.get(1);
HuffmanNode parent=new HuffmanNode(leftNode.no+rightNode.no);
parent.left=leftNode;
parent.right=rightNode;
list.remove(leftNode);
list.remove(rightNode);
list.add(parent);
}
return list.get(0);
}
}
class HuffmanNode implements Comparable<HuffmanNode>{
int no;
HuffmanNode left;
HuffmanNode right;
public HuffmanNode(int no) {
this.no = no;
}
public void perOrder() {
System.out.println(this);
if (this.left != null) {
this.left.perOrder();
}
if (this.right!=null){
this.right.perOrder();
}
}
@Override
public String toString() {
return "HuffmanNode{" +
"no=" + no +
'}';
}
@Override
public int compareTo(HuffmanNode o) {
return this.no-o.no;
}
}
哈夫曼编码
基本介绍
- 1)赫夫曼编码也翻译为――哈夫曼编码(Huffiman Coding),又称霍夫曼编码,是一种编码方式,属于一种程序算法
- 2)赫夫曼编码是赫哈夫曼树在电讯通信中的经典的应用之一。
- 3)赫夫曼编码广泛地用于数据文件压缩。其压缩率通常在20%~90%之间
- 4)赫夫曼码是可变字长编码(VLC)的一种。Huffiman于1952年提出一种编码方法,称之为最佳编码
原理剖析 通信领域中信息的处理方式-定长编码 i like like like java do you like a java共40个字符(包括空格)
对应Ascii码
105 32 108 105 107 101 32 108 105 107 101 32108 105 107101 32106 97 11897 32100 111 32 121 11111732108 1051101 32 97 32 106 97 11897
对应的二进制
01101001 001000000110110001101001011010110110010100100000 0110110001101001 011010110110010100100000 01101100 01101001011010110110010100100000 01101010011000010111011001100001 0010000001100100 0110111100100000 01111001011011110111010100100000 01101100011010010110101101100101 0010000001100001 00100000 0110101001100001011101100110000111
按照二进制来传递信息,总的长度是359(包括空格)
哈夫曼编码步骤 传输的字符串
- 1)i like like like java do you like a java
- 2)
d:1 y:1 u:1 j:2 v:2 o:2 l:4 k:4 e:4 i:5 a:5 :9 各个字符对应的个数 3)按照上面字符出现的次数构建一颗赫夫曼树,次数作为权值
- 4)根据赫夫曼树,给各个字符,规定编码(前缀编码),向左的路径为О向右的路径为1,编码如下:
o:1000
u:10010
d:100110
y:100111
i:101
a:110
k:1110
:1111
j:0000
v:0001
l:001
:01
- 5)按照上面的赫夫曼编码,我们的"ilike like like java do you like a
java"字符串对应的编码为(注意这里我们使用的无损压缩)
1010100110111101111010011011110111101001101111011110100001100001110011001111000011001111000100100100110111101111011100100001100001110
- 通过赫夫曼编码处理﹐长度为︰1336)长度为:133
- 说明: 原来长度是359,压缩了(359-133)/ 359= 62.9%
package dataStructure.Tree;
import java.util.*;
public class HuffmanCoding {
static Map<Byte,String> HuffmanCodeMap =new HashMap<>();
static StringBuilder stringBuilder=new StringBuilder();
public static void main(String[] args) {
String s="hello word";
byte[] bytes=s.getBytes();
List<HuffmanNode1> nodes = getNodes(bytes);
HuffmanNode1 root = creatHuffmanTree(nodes);
perOrder(root);
getCoding(root,"",stringBuilder);
System.out.println(HuffmanCodeMap);
}
public static void getCoding(HuffmanNode1 node,String code,StringBuilder stringBuilder){
StringBuilder stringBuilder1 = new StringBuilder(stringBuilder);
stringBuilder1.append(code);
if (node!=null){
if (node.key==null){
getCoding(node.left,"0",stringBuilder1);
getCoding(node.right,"1",stringBuilder1);
}else {
HuffmanCodeMap.put(node.key,stringBuilder1.toString());
}
}
}
public static List<HuffmanNode1> getNodes(byte[] bytes) {
List<HuffmanNode1> list=new ArrayList<>();
Map<Byte,Integer> map=new HashMap<>();
for (byte b:bytes){
map.merge(b, 1, Integer::sum);
}
for (Map.Entry<Byte,Integer> m:map.entrySet()){
list.add(new HuffmanNode1(m.getKey(),m.getValue()));
}
return list;
}
public static HuffmanNode1 creatHuffmanTree(List<HuffmanNode1> list){
while (list.size()>1){
Collections.sort(list);
HuffmanNode1 leftNode = list.get(0);
HuffmanNode1 rightNode = list.get(1);
HuffmanNode1 parent=new HuffmanNode1(null,leftNode.no+rightNode.no);
parent.left=leftNode;
parent.right=rightNode;
list.remove(leftNode);
list.remove(rightNode);
list.add(parent);
}
return list.get(0);
}
public static void perOrder(HuffmanNode1 root){
if (root==null){
return;
}
root.perOrder();
}
}
class HuffmanNode1 implements Comparable<HuffmanNode1>{
int no;
Byte key;
HuffmanNode1 left;
HuffmanNode1 right;
public HuffmanNode1(Byte key,int no ) {
this.no = no;
this.key = key;
}
public void perOrder() {
System.out.println(this);
if (this.left != null) {
this.left.perOrder();
}
if (this.right!=null){
this.right.perOrder();
}
}
@Override
public String toString() {
return "HuffmanNode1{" +
"no=" + no +
", key=" + key +
'}';
}
@Override
public int compareTo(HuffmanNode1 o) {
return this.no-o.no;
}
}
二叉排序树
基本介绍 二叉排序树 BST:(Binary Sort(Search)Tree),对于二叉排序树的任何一个非叶子节点,要求左子节点的值比当前节点的值小,右子节点的值比当前节点的值大。
如果有相同的值,可以将该节点放在左子节点或右子节点
二叉排序树的删除 三种情况
(1)需求先去找到要删除的结点targetNode (2)找到 targetNode 的父结点parent (3)确定targetNode是parent的左子结点还是右子结点 (4)根据前面的情况来对应删除 左子结点parent.left = null 右子结点parent.right = null;
(1)需求先去找到要删除的结点targetNode (2)找到targetNode 的父结点 parent (3)确定targetNode的子结点是左子结点还是右子结点 (4) targetNode是 parent的左子结点还是右子结点 (5)如果targetNode有左子结点
(1)需求先去找到要删除的结点targetNode (2)找到targetNode的父结点 parent (3)从targetNode的右子树找到最小的结点 (4)用一个临时变量,将最小结点的值保存temp=11 (5)删除该最小结点 (6)targetNode.value = temp
package dataStructure.Tree;
public class BinarySortTree {
public static void main(String[] args) {
int[] arr ={7,3,10,12,5,1,9};
SortTree node=new SortTree();
for (int j : arr) {
node.add(new BinarySortTreeNode(j));
}
node.delete(7);
node.perOrder();
}
}
class SortTree{
private BinarySortTreeNode root;
public void add(BinarySortTreeNode node){
if (root==null){
root=node;
}else {
root.add(node);
}
}
public int deleteMin(BinarySortTreeNode node){
BinarySortTreeNode target=node;
if (target.left!=null){
target=target.left;
}
delete(target.no);
return target.no;
}
public void delete(int no) {
if (root == null) {
return;
}
BinarySortTreeNode search = root.search(no);
if (search == null) {
return;
}
BinarySortTreeNode searchParent = root.searchParent(no);
if (search.left == null && search.right == null) {
if (searchParent.left != null && searchParent.left == search) {
searchParent.left = null;
} else if (searchParent.right != null && searchParent.right == search) {
searchParent.right = null;
}
} else if (search.left!=null&&search.right!=null) {
search.no= deleteMin(search.right);
}else {
if (search.left!=null){
if (searchParent.left.no==no){
searchParent.left=search.left;
}else if (searchParent.right.no==no){
searchParent.right=search.left;
}
}else {
if (searchParent.left.no==no){
searchParent.left=search.right;
}else if (searchParent.right.no==no){
searchParent.right=search.right;
}
}
}
}
public void search(int no){
if (root==null){
return;
}
root.search(no);
}
public void searchParent(int no){
if (root==null){
return;
}
root.searchParent(no);
}
public void perOrder() {
if (root == null) {
return;
}
root.perOrder();
}
}
class BinarySortTreeNode{
int no;
BinarySortTreeNode left;
BinarySortTreeNode right;
public BinarySortTreeNode(int no) {
this.no = no;
}
public BinarySortTreeNode search(int no){
if (this.no==no){
return this;
}else if (no<this.no){
if (this.left==null){
return null;
}
return this.left.search(no);
}else {
if (this.right==null){
return null;
}
return this.right.search(no);
}
}
public BinarySortTreeNode searchParent(int no) {
if ((this.left != null && this.left.no == no) || (this.right != null && this.right.no == no)) {
return this;
} else {
if (no < this.no && this.left != null) {
return this.left.searchParent(no);
} else if (no >= this.no && this.right != null) {
return this.right.searchParent(no);
} else {
return null;
}
}
}
public void add(BinarySortTreeNode node){
if (node==null){
return;
}
if (node.no<this.no){
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 perOrder(){
System.out.println(this);
if (this.left!=null){
this.left.perOrder();
}
if (this.right!=null){
this.right.perOrder();
}
}
@Override
public String toString() {
return "BinarySortTreeNode{" +
"no=" + no +
'}';
}
}
平衡二叉树
基本介绍
- 1)平衡二叉树也叫平衡二叉搜索树(Self-balancing binary search tree)又被称为AVL树,可以保证查询效率较高。
2)具有以下特点:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵
左旋转
-
1.创建一个新的节点newNode(以4这个值创建),创建一个新的节点,值等于当前根节点的值 -
2.把新节点的左子树设置了当前节点的左子树 new Node.left= left -
3.把新节点的右子树设置为当前节点的右子树的左子树 new Node.right =right.left; -
4.把当前节点的值换为右子节点的值 value=right.value; -
5.把当前节点的右子树设置成右子树的右子树 right=right.right; -
6.把当前节点的左子树设置为新节点 left=newLeft;
右旋转
- 1.创健一个新的节点newNode(以10这个值创建).创建一个新的节点,值等于当前根节点的值
- 2.把新节点的右子树设置了当前节点的右子树
newNode.right= right - 3.把新节点的左子树设置为当前节点的左子树的右子树
newNode.let =left.right; - 4.把当前节点的值换为左子节点的值
value-left.value; - 5.把当前节点的左子树设置成左子树的佐子树
left=left.left; - 6.把当前节点的右子树设置为新节点
right=newLeft;
双旋转 前面的两个数列,进行单旋转(即一次旋转)就可以将非平衡二叉树转成平衡二叉树,但是在某些情况下,单旋转不能完成平衡二叉树的转换。
问题分析 1.当符号右旋转的条件时 2.如果它的左子树的右子树高度大于它的左子树的高度 3.先对当前这个结点的左节点讲行左旋转 4.在对当前结点进行右旋转的操作即可
package dataStructure.Tree;
public class AVLTree {
public static void main(String[] args) {
int[] arr={10,11,7,6,8,9};
AVL avl=new AVL();
for (int i = 0; i < arr.length; i++) {
avl.add(new AVLTreeNode(arr[i]));
}
System.out.println(avl.getRoot().maxLength());
System.out.println(avl.getRoot().leftLength());
System.out.println(avl.getRoot().rightLength());
System.out.println(avl.getRoot().right);
}
}
class AVL{
private AVLTreeNode root;
public AVLTreeNode getRoot() {
return root;
}
public void add(AVLTreeNode node){
if (root==null){
root=node;
}else {
root.add(node);
}
}
public void perOrder() {
if (root == null) {
return;
}
root.perOrder();
}
}
class AVLTreeNode{
int no;
AVLTreeNode left;
AVLTreeNode right;
public AVLTreeNode(int no) {
this.no = no;
}
public int leftLength(){
if (left!=null){
return left.maxLength();
}else {
return 0;
}
}
public int rightLength(){
if (right!=null){
return right.maxLength();
}else {
return 0;
}
}
public int maxLength(){
return Math.max(this.left==null?0:this.left.maxLength(),this.right==null?0:this.right.maxLength())+1;
}
public void leftRotate(){
AVLTreeNode node=new AVLTreeNode(this.no);
node.left=this.left;
node.right=this.right.left;
this.no=this.right.no;
this.right=this.right.right;
this.left=node;
}
public void rightRotate(){
AVLTreeNode node=new AVLTreeNode(this.no);
node.right=this.right;
node.left=this.left.right;
this.no=this.left.no;
this.left=this.left.left;
this.right=node;
}
public void add(AVLTreeNode node){
if (node==null){
return;
}
if (node.no<this.no){
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 (rightLength()-leftLength()>1){
if (left!=null&&right.maxLength()>left.maxLength()){
right.rightRotate();
}
leftRotate();
return;
}
if (leftLength()-rightLength()>1){
if (right!=null&&left.maxLength()>right.maxLength()){
left.leftRotate();
}
rightRotate();
}
}
public void perOrder(){
System.out.println(this); if (this.left!=null){
this.left.perOrder();
}
if (this.right!=null){
this.right.perOrder();
}
}
@Override
public String toString() {
return "AVLTreeNode{" +
"no=" + no +
'}';
}
}
图
基本介绍 图是一种数据结构,其中结点可以具有零个或多个相邻元素。两个结点之间的连接称为边。结点也可以称为顶点。如图: 图的概念
- 1)顶点(vertex):图中的点
- 2)边(edge):图中的线
- 3)路径:点到点之间的路线
- 4)无向图
- 5)有向图
6)带权图
图的表示方式 图的表示方式有两种:二维数组表示(邻接矩阵),链表表示(邻接表)。
图形中顶点之间相邻关系的矩阵,对于n个顶点的图而言,矩阵是的row和 col表示的是1…n个点
邻接矩阵需要为每个顶点都分配n个边的空间,其实有很多边都是不存在,会造成空间的一定损失.
package dataStructure.graph;
import java.util.*;
public class Graph {
public static void main(String[] args) {
Graph graph=new Graph(5);
String[] vertexes={"A","B","C","D","E"};
for (String vertex:vertexes){
graph.insertVertex(vertex);
}
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.graphList();
}
private final ArrayList<String> vertexList;
private final int[][] matrix;
private int edgeNum;
private final boolean[] isVisited;
public Graph(int n){
vertexList=new ArrayList<>(n);
matrix=new int[n][n];
edgeNum=0;
isVisited=new boolean[n];
}
public int getFirstNeighbor(int index){
for (int i = 0; i < vertexList.size(); i++) {
if (matrix[index][i]>0){
return i;
}
}
return -1;
}
public int getNextNeighbor(int lastIndex,int index){
for (int i = index+1; i < vertexList.size(); i++) {
if (matrix[lastIndex][i]>0){
return i;
}
}
return -1;
}
public int getVertexNum(){
return vertexList.size();
}
public int getEdgeNum(){
return edgeNum;
}
public void graphList(){
for (int[] value:matrix){
System.out.println(Arrays.toString(value));
}
}
public String getValueByIndex(int i){
return vertexList.get(i);
}
public void insertVertex(String vertex){
vertexList.add(vertex);
}
public void insertEdge(int i ,int j,int weight){
matrix[i][j]=weight;
matrix[j][i]=weight;
edgeNum++;
}
}
邻接表的实现只关心存在的边,不关心不存在的边。因此没有空间浪费,邻接表由数组+链表组成
图的遍历
所谓图的遍历,即是对结点的访问。一个图有那么多个结点,如何遍历这些结点,需要特定策略,一般有两种访问策略:
1)深度优先遍历顺序为1->2->4->8->5->3->6->7 2)广度优先算法的遍历顺序为:1->2->3->4->5->6->7->8
深度优先遍历
深度优先遍历基本思想 图的深度优先搜索。
- 1)深度优先遍历,从初始访问结点出发,初始访问结点可能有多个邻接结点,深度优先遍历的策略就是首先访问第一个邻接结点,然后再以这个被访问的邻接结点作为初始结点,访问它的第一个邻接结点,可以这样理解:每次都在访问完当前结点后首先访问当前结点的第一个邻接结点。
- 2)我们可以看到,这样的访问策略是优先往纵向挖掘深入,而不是对一个结点的所有邻接结点进行横向访问。
- 3)显然,深度优先搜索是一个递归的过程
深度优先遍历算法步骤
- 1)访问初始结点v,并标记结点v为已访问。
- 2)查找结点v的第一个邻接结点 w。
- 3)若w存在,则继续执行
- 4,如果w不存在,则回到第1步,将从v的下一个结点继续。 若w未被访问,对w进行深度优先遍历递归(即把w当做另一个v,然后进行步骤123)。
- 5)查找结点v的w邻接结点的下一个邻接结点,转到步骤3。
public void DFS(boolean[] isVisited,int index){
System.out.print(getValueByIndex(index)+"=>");
isVisited[index]=true;
int firstNeighbor = getFirstNeighbor(index);
while (firstNeighbor!=-1){
if (!isVisited[firstNeighbor]){
DFS(isVisited,firstNeighbor);
}else {
firstNeighbor=getNextNeighbor(index,firstNeighbor);
}
}
}
public void DFS(){
for (int i = 0; i < getVertexNum(); i++) {
if (!isVisited[i]){
DFS(isVisited,i);
}
}
}
`
广度优先遍历
广度优先遍历基本思想
广度优先遍历算法步骤
- 1)访问初始结点v并标记结点v为已访问。
- 2)结点v入队列
- 3)当队列非空时,继续执行,否则算法结束。
- 4)出队列,取得队头结点u。
- 5)查找结点u的第一个邻接结点w.
- 6)若结点u的邻接结点w不存在,则转到步骤3;否则循环执行以下三个步骤:
6.1若结点w尚未被访问,则访问结点w并标记为已访问。 6.2 结点w入队列 6.3查找结点u的继w邻接结点后的下一个邻接结点w,转到步骤6。
public void BFS(boolean[] isVisited,int index){
System.out.print(getValueByIndex(index)+"=>");
isVisited[index]=true;
LinkedList<Integer> list=new LinkedList<>();
list.add(index);
while (!list.isEmpty()){
Integer first = list.removeFirst();
int firstNeighbor = getFirstNeighbor(first);
while (firstNeighbor!=-1){
if (!isVisited[firstNeighbor]){
System.out.print(getValueByIndex(firstNeighbor)+"=>");
isVisited[firstNeighbor]=true;
list.add(firstNeighbor);
}
firstNeighbor=getNextNeighbor(index,firstNeighbor);
}
}
}
public void BFS(){
for (int i = 0; i < getVertexNum(); i++) {
if (!isVisited[i]){
BFS(isVisited,i);
}
}
}
算法分析
递归
递归的概念 递归就是方法自己调用自己,每次调用时传入不同的变量.递归有助于解决复杂的问题,同时可以让代码变得简洁。
递归遵守的重要规则
- 1)执行一个方法时,就创建一个新的受保护的独立空间(栈空间)
- 2)方法的局部变量是独立的,不会相互影响
- 3)如果方法中使用的是引用类型变量(比如数组),就会共享该引用类型的数据.
- 4)递归必须向退出递归的条件逼近,否则就是无限递归,出现StackOverflowError,死循环
- 5)当一个方法执行完毕,或者遇到return,就会返回,遵守谁调用,就将结果返回给谁,同时当方法执行完毕或 者返回时,该方法也就执行完毕
迷宫问题
初始位置【1,1】,到达位置【6,5】
package algorithmAnalysis.recursion;
public class MazeProblem {
public static void main(String[] args) {
int[][] migong=new int[8][7];
for (int i = 0; i < 7; i++) {
migong[0][i]=1;
migong[7][i]=1;
}
for (int i = 0; i < 8; i++) {
migong[i][0]=1;
migong[i][6]=1;
}
migong[3][1]=1;
migong[3][2]=1;
for (int i = 0; i < 8; i++) {
for (int j = 0; j < 7; j++) {
System.out.print(migong[i][j]+" ");
}
System.out.println();
}
System.out.println("-------------------");
way(migong,1,1);
for (int i = 0; i < 8; i++) {
for (int j = 0; j < 7; j++) {
System.out.print(migong[i][j]+" ");
}
System.out.println();
}
}
public static boolean way(int[][] migong,int i,int j){
if(migong[6][5]==2){
return true;
}else {
if (migong[i][j]==0){
migong[i][j]=2;
if(way(migong,i+1,j)){
return true;
}
else if(way(migong,i,j+1)){
return true;
}
else if(way(migong,i-1,j)){
return true;
}
else if(way(migong,i,j-1)){
return true;
}else {
migong[i][j]=3;
return false;
}
}else {
return false;
}
}
}
}
1)小球得到的路径,和程序员设置的找路策略有关即:找路的上下左右的顺序相关 2)再得到小球路径时,可以先使用(下右上左),再改成(上右下左),看看路径是不是有变化
八皇后问题
问题介绍 在8×8格的国际象棋上摆放八个皇后,使其不能互相攻击,即:任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法(92种)。
思路分析
- 1)第一个皇后先放第一行第一列
- 2)第二个皇后放在第二行第一列、然后判断是否OK,如果不OK,继续放在第二列、第三列、依次把所有列都放完,找到一个合适
- 3) 继续第三个皇后,还是第一列、第二列……直到第8个皇后也能放在一个不冲突的位置,算是找到了一个正确解
- 4)当得到一个正确解时,在栈回退到上一个栈时,就会开始回溯,即将第一个皇后,放到第 一列的所有正确解, 全部得到.
- 5)然后回头继续第一个皇后放第二列,后面继续循环执行1,2,3,4的
package algorithmAnalysis.recursion;
public class EightQueen {
int max=8;
int[] array=new int[max];
static int count;
public static void main(String[] args) {
EightQueen queen=new EightQueen();
queen.check(0);
System.out.println(count);
}
public void check(int n){
if(n==max){
count++;
print();
return;
}
for (int i = 0; i < max; i++) {
array[n]=i;
if (judge(n)){
check(n+1);
}
}
}
public void print(){
for (int i = 0; i < max; i++) {
System.out.print(array[i]+" ");
}
System.out.println();
}
public boolean judge(int n){
for (int i = 0; i < n; i++) {
if(array[i]==array[n]||Math.abs(n-i)==Math.abs(array[n]-array[i])) {
return false;
}
}
return true;
}
}
排序
排序算法的介绍 排序也称排序算法(Sort Algorithm),排序是将一组数据,依指定的顺序进行排列的过程。 排序的分类:
- 1)内部排序: 指将需要处理的所有数据都加载到内部存储器(内存)中进行排序。
- 2)外部排序法: 数据量过大,无法全部加载到内存中,需要借助外部存储(文件等)进行排序。
3)常见的排序算法分类
时间复杂度
时间频度 时间频度:一个算法花费的时间与算法中语句的执行次数成正比例,哪个算法中语句执行次数多,它花费时间就多。一个算法中的语句执行次数称为语句频度或时间频度。记为T(n)。 计算时间复杂度的方法:
忽略常数项
用常数1代替运行时间中的所有加法常数T(n)=n2+7n+6 =>T(n)=n2+7n+1
忽略低次项
修改后的运行次数函数中,只保留最高阶项‘T(n)=n2+7n+1 => T(n)=n2
忽略系数
去除最高阶项的系数T(n)= n2=>T(n)= n2=> O(n2)
常见的时间复杂度
int i=1;
int j=2;
++i;
++j;
int m=i+j;
int i=1;
while(i<n){
i++;
}
j=1;
for(i=1; i<n; ++i){
j++:
}
for(m=1; m<n; m++){
i=1;
while(i<n){
i=i*2;
}
}
n=1;
for(i=1;i<n;i++){
for(j=1;j<n; j++){
n++;
}
}
- 6)立方阶O(n^3)
- 7)k次方阶O(n^k)
- 8)指数阶O(2^n)
常见的算法时间复杂度由小到大依次为:0(1)<0(log2n)<0(n)<0(nlog2n)<0(n2)<O(n3)<O(nk)<o(2n),随着问题规模n的不断增大,时间复杂度不断增大,算法的执行效率越低
冒泡排序
基本思想 通过对待排序序列从前向后(从下标较小的元素开始),依次比较相邻元素的值,若发现逆序则交换,使值较大的元素逐渐从前移向后部,就象水底下的气泡一样逐渐向上冒。
- (1)一共进行数组的大小-1次的循环
- (2)每一趟排序的次数在逐渐的减少
- (3)如果我们发现在某趟排序中,没有发生一次交换,可以提前结束冒泡排序。这个就是优化
package algorithmAnalysis.sort;
import java.util.Arrays;
public class Bubbling {
public static void main(String[] args) {
int arr[]={5,6,31,5,6};
bubbleSort(arr);
System.out.println(Arrays.toString(arr));
}
public static void bubbleSort(int[] arr){
int temp;
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;
temp=arr[j+1];
arr[j+1]=arr[j];
arr[j]=temp;
}
}
if(!flag){
break;
}
flag=false;
}
}
}
选择排序
基本思想 选择排序(select sorting)也是一种简单的排序方法。它的基本思想是:第一次从arr[0]~arr[n-1]中选取最小值,与arr[0]交换,第二次从 ar[1]~-arr[n-1]中选取最小值,与arr[1]交换,第三次从 arr[2] ~ arr[n-1]中选取最小值,与arr[2]交换,…,第i次从 arr[i-l1]~ arr[n-1]中选取最小值,与arr[i-1]交换,…,第n-1次从arr[n-2]~arr[n-1]中选取最小值,与arr[n-2]交换,总共通过n-1次,得到一个按排序码从小到大排列的有序序列。
说明:
- 1.选择排序一共有数组大小-1轮排序
- 2.每1轮排序,又是一个循环,循环的规则(代码)
- 2.1先假定当前这个数是最小数
- 2.2然后和后面的每个数进行比较,如果发现有比当前数更小的数,就重新确定最小数,并得到下标
- 2.3当遍历到数组的最后时,就得到本轮最小数和下标
package algorithmAnalysis.sort;
import java.util.Arrays;
public class Choice {
public static void main(String[] args) {
int arr[]={5,3,5,1,6};
selectSort(arr);
System.out.println(Arrays.toString(arr));
}
public static void selectSort(int[] arr){
for (int i = 0; i < arr.length-1; i++) {
int minArr=i;
int min=arr[i];
for (int j = i+1; j < arr.length; j++) {
if (min>arr[j]){
minArr=j;
min=arr[j];
}
}
if (minArr!=i){
arr[minArr]=arr[i];
arr[i]=min;
}
}
}
}
插入排序
插入排序法思想: 插入排序(Insertion Sorting)的基本思想是:把n个待排序的元素看成为一个有序表和一个无序表,开始时有序表中只包含一个元素,无序表中包含有n-1个元素,排序过程中每次从无序表中取出第一个元素,把它的排序码依次与有序表元素的排序码进行比较,将它插入到有序表中的适当位置,使之成为新的有序表。
package algorithmAnalysis.sort;
import java.util.Arrays;
public class Insert {
public static void main(String[] args) {
int arr[]={5,3,5,1,6};
insertSort(arr);
System.out.println(Arrays.toString(arr));
}
public static void insertSort(int[] arr){
for (int i = 1; i < arr.length; i++) {
int insertNum=arr[i];
int insertIndex=i-1;
while (insertIndex>=0&&insertNum<arr[insertIndex]){
arr[insertIndex+1]=arr[insertIndex];
insertIndex--;
}
arr[insertIndex+1]=insertNum;
}
}
}
希尔排序
思想线路 希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止
数组arr = {2,3,4,5,6,1}这时需要插入的数1(最小),这样的过程是: {2,3,4,5,6,6} {2,3,4,5,5,6} {2,3,4,4,5,6} {2,3,3,4,5,6} {2.2,3,4,5,6} {1,2,3,4,5,6} 结论:当需要插入的数是较小的数时,后移的次数明显增多,对效率有影响.
package algorithmAnalysis.sort;
import java.util.Arrays;
public class Shell {
public static void main(String[] args) {
int[] arr={2,0,35,14,6,82,10,3,14,5};
shellSort(arr);
System.out.println(Arrays.toString(arr));
}
public static void shellSort(int[] arr){
for (int i= arr.length/2;i>0;i/=2){
for (int j=i;j< arr.length;j++){
int index=j;
int temp=arr[j];
while (index-i>=0&&temp<arr[index-i]){
arr[index]=arr[index-i];
index-=i;
}
arr[index]=temp;
}
}
}
}
快速排序
基本思想 快速排序(Quicksort)是对冒泡排序的一种改进。通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列
package algorithmAnalysis.sort;
import java.util.Arrays;
public class Quick {
public static void main(String[] args) {
int[] arr={5,3,0,-1,6};
quickSort(arr,0, arr.length-1);
System.out.println(Arrays.toString(arr));
}
public static void quickSort(int[] arr,int left,int right){
int r=right;
int l=left;
int m=arr[(r+l)/2];
int temp=0;
while (l<r){
while (arr[l]<m){
l+=1;
}
while (arr[r]>m){
r-=1;
}
if (l>=r){
break;
}
temp=arr[r];
arr[r]=arr[l];
arr[l]=temp;
}
if (l==r){
l++;
r--;
}
if (left<r){
quickSort(arr,left,r);
}
if (right>l){
quickSort(arr,l,right);
}
}
}
归并排序
思想 归并排序(mergeSort)是利用归并的思想实现的排序方法,该算法采用经典的分治(divide-and-conquer)策略(分治法将问题分(divide)成一些小的问题然后递归求解,而治(conquer)的阶段则将分的阶段得到的各答案"修补"在一起,即分而治之)。 合并相邻有序子序列
package algorithmAnalysis.sort;
import java.util.Arrays;
public class Merge {
public static void main(String[] args) {
int[] arr={2,5,35,1,6,8,7,0};
int[] temp=new int[arr.length];
divide(arr,0,arr.length-1,temp);
System.out.println(Arrays.toString(arr));
}
public static void divide(int[] arr,int left,int right,int[] temp) {
if (left < right) {
int mid = (left + right) / 2;
divide(arr, left, mid, temp);
divide(arr, mid + 1, right, temp);
mergeSort(arr, left, right, mid, temp);
}
}
public static void mergeSort(int[] arr,int left,int right,int mid,int[] temp){
int l=left;
int r=mid+1;
int tempIndex=0;
while (l<=mid&&r<=right){
if (arr[l]<=arr[r]){
temp[tempIndex]=arr[l];
tempIndex++;
l++;
}else {
temp[tempIndex]=arr[r];
tempIndex++;
r++;
}
}
while (l<=mid){
temp[tempIndex]=arr[l];
l++;
tempIndex++;
}
while (r<=right){
temp[tempIndex]=arr[r];
r++;
tempIndex++;
}
tempIndex=0;
int tempLeft=left;
while (tempLeft<=right){
arr[tempLeft]=temp[tempIndex];
tempIndex++;
tempLeft++;
}
}
}
基数排序
基数排序(桶排序)介绍
- 1)基数排序(radix sort)属于“分配式排序”(distribution sort),又称“桶子法”(bucket
sort)或bin sort,它是通过键值的各个位的值,将要排序的元素分配至某些“桶”中,达到排序的作用 - 2)基数排序法是属于稳定性的排序,基数排序法的是效率高的稳定性排序法
- 3)基数排序(Radix Sort)是桶排序的扩展
- 4)基数排序实现:将整数按位数切割成不同的数字,然后按每个位数分别比较。
基数排序基本思想 将所有待比较数值统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后,数列就变成一个有序序列。
package algorithmAnalysis.sort;
import java.util.Arrays;
public class Radix {
public static void main(String[] args) {
int[] arr={2,30,65,1,89,3};
radixSort(arr);
System.out.println(Arrays.toString(arr));
}
public static void radixSort(int[] arr) {
int max = arr[0];
for (int j : arr) {
if (j > max) {
max = j;
}
}
int maxNum = (max + "").length();
int[][] bucket = new int[10][arr.length];
int[] bucketCount = new int[10];
for (int i = 0, n = 1; i < maxNum; i++, n *= 10) {
for (int k : arr) {
int num = k / n % 10;
bucket[num][bucketCount[num]] = k;
bucketCount[num]++;
}
int index = 0;
for (int k = 0; k < bucketCount.length; k++) {
if (bucketCount[k] != 0) {
for (int j = 0; j < bucketCount[k]; j++) {
arr[index] = bucket[k][j];
index++;
}
}
bucketCount[k] = 0;
}
}
}
}
排序算法比较
相关术语解释:
- 1)稳定:如果a原本在b前面,而a=b,排序之后a仍然在b的前面;
- 2)不稳定:如果a原本在b的前面,而a=b,排序之后a可能会出现在b的后面;3)内排序:所有排序操作都在内存中完成;
- 4)外排序:由于数据太大,因此把数据放在磁盘中,而排序通过磁盘和内存的数据传输才能进行;
- 5)时间复杂度:一个算法执行所耗费的时间。
- 6)空间复杂度:运行完一个程序所需内存的大小。
- 7)n:数据规模
- 8)k:“桶”的个数
- 9)In-place:不占用额外内存10)Out-place:占用额外内存
堆排序
基本介绍
- l)堆排序是利用堆这种数据结构而设计的一种排序算法,堆排序是一种选择排序,它的最坏,最好,平均时间复杂度均为O(nlogn),它也是不稳定排序。
- 2)堆是具有以下性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆,注意︰没有要求结点的左孩子的值和右孩子的值的大小关系。
- 3)每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆
- 4)大顶堆举例
大顶堆特点:arr[i]>=arr[2i+1]&& arr[i]>=arr[2i+2] i对应第几个节点,i从o开始编号
小顶堆: arr[i]=arr[2i+1]&& arr[i]<= arr[2i+2] i对应第几个节点,i从o开始编号
堆排序基本思想
- 1)将待排序序列构造成一个大顶堆
- 2)此时,整个序列的最大值就是堆顶的根节点。
- 3)将其与末尾元素进行交换,此时末尾就为最大值。
- 4)然后将剩余n-1个元素重新构造成一个堆,这样会得到n个元素的次小值。如此反复执行,便能得到一个有序序列了。
举例说明 步骤一构造初始堆。将给定无序序列构造成一个大顶堆(一般升序采用大顶堆,降序采用小顶堆)。
原始的数组[4.6.8.5.9]
- .假设给定无序序列结构如下
- .此时我们从最后一个非叶子结点开始(叶结点自然不用调整,第一个非叶子结点arr.length/2-1=5/2-1=1,也就是下面的6结点),从左至右,从下至上进行调整。
3).找到第二个非叶节点4,由于[4,9,8]中9元素最大,4和9交换。 4)这时,交换导致了子根[4,5,6]结构混乱,继续调整,[4,5,6]中6最大,交换4和6。 此时,我们就将一个无序序列构造成了一个大顶堆。
步骤二将堆顶元素与末尾元素进行交换,使末尾元素最大。然后继续调整堆,再将堆顶元素与末尾元素交换得到第二大元素。如此反复进行交换、重建、交换。
1).将堆顶元素9和末尾元素4进行交换 2) .重新调整结构,使其继续满足堆定义 3) .再将堆顶元素8与末尾元素5进行交换,得到第二大元素8. 4)后续过程,继续进行调整,交换,如此反复进行,最终使得整个序列有序
堆排序的基本思路: 1).将无序序列构建成一个堆,根据升序降序需求选择大顶堆或小顶堆; 2).将堆顶元素与末尾元素交换,将最大元素"沉"到数组末端; 3).重新调整结构,使其满足堆定义,然后继续交换堆顶元素与当前末尾元素,反复执行调整+交换步骤,直到整个序列有序。
package algorithmAnalysis.sort;
import java.util.Arrays;
public class Heap {
public static void main(String[] args) {
int[] arr={2,5,68,11,0};
heapSort(arr);
System.out.println(Arrays.toString(arr));
}
public static void heapSort(int[] arr){
int temp=0;
for (int i = arr.length/2-1; i >=0 ; i--) {
adjustHeap(arr,i,arr.length);
}
for (int i = arr.length-1; i >0 ; i--) {
temp=arr[i];
arr[i]=arr[0];
arr[0]=temp;
adjustHeap(arr,0, i);
}
}
public static void adjustHeap(int[] arr,int i,int length){
int temp=arr[i];
for (int j = i*2+1; j <length ; j=j*2+1) {
if ((j+1)<length&&arr[j]<arr[j+1]){
j++;
}
if (temp<arr[j]){
arr[i]=arr[j];
i=j;
}else {
break;
}
}
arr[i]=temp;
}
}
查找
线性查找
判断数列中是否包含此名称【顺序查找】。如果找到了,就提示找到,并给出下标值。
package algorithmAnalysis.search;
public class Linear {
public static void main(String[] args) {
int[] arr={1,2,3,4,5,6};
System.out.println(linearSearch(arr, 3));
}
public static int linearSearch(int[] arr,int num){
for (int i = 0; i < arr.length; i++) {
if (arr[i]==num){
return i;
}
}
return -1;
}
}
二分查找
思路分析
- 1.首先确定该数组的中间的下标mid= (left +right)/2
- 2.然后让需要查找的数和arr[mid]比较
2.1 num> arr[mid],说明你要查找的数在mid的右边,因此需要递归的向右查找 2.2num<arr[mid],说明你要查找的数在mid的左边,因此需要递归的向左查找 2.3num ==arr[mid]说明找到,就返回
1)找到就结束递归 2)递归完整个数组,仍然没有找到num,也需要结束递归left >right就需要退出
递归
package algorithmAnalysis.search;
import java.util.ArrayList;
public class Binary {
public static void main(String[] args) {
int[] arr={1,2,3,3,4,5,6};
System.out.println(binarySearch(arr, 3, 0, arr.length - 1));
}
public static ArrayList<Integer> binarySearch(int[] arr, int num, int left, int right){
int mid=(left+right)/2;
int midVal=arr[mid];
if (left>right){
return new ArrayList<Integer>();
}
else if (num<midVal){
return binarySearch(arr,num,left,mid-1);
}
else if (num>midVal){
return binarySearch(arr,num,mid+1,right);
}else {
ArrayList<Integer> list=new ArrayList<>();
int temp=mid-1;
while (temp >= 0 && arr[temp] == num) {
list.add(temp);
temp -= 1;
}
list.add(mid);
temp=mid+1;
while (temp <= arr.length - 1 && arr[temp] == num) {
list.add(temp);
temp += 1;
}
return list;
}
}
}
非递归
package algorithmAnalysis.search;
import java.util.ArrayList;
public class Binary {
public static void main(String[] args) {
int[] arr={1,2,3,3,4,5,6};
System.out.println(binary(arr, 2));
}
public static int binary(int[] arr,int num){
int left=0;
int right=arr.length-1;
while (left<=right){
int mid=(left+right)/2;
if (arr[mid]==num){
return mid;
}
else if (num<arr[mid]){
right=mid-1;
}else {
left=mid+1;
}
}
return -1;
}
}
插值查找
1)插值查找原理介绍: 插值查找算法类似于二分查找,不同的是插值查找每次从自适应mid处开始查找。
2)插值查找公式:
int mid=left+(num-left)/(arr[right]-arr[left])*(right-left);
插值查找注意事项: 1)对于数据量较大,关键字分布比较均匀的查找表来说,采用插值查找,速度较快. 2)关键字分布不均匀的情况下,该方法不一定比折半查找要好
package algorithmAnalysis.search;
import java.util.ArrayList;
public class Interpolation {
public static void main(String[] args) {
int[] arr={1,2,3,4,5,6};
System.out.println(binarySearch(arr, 1, 0, arr.length - 1));
}
public static int binarySearch(int[] arr, int num, int left, int right){
int mid=left+(num-left)/(arr[right]-arr[left])*(right-left);
int midVal=arr[mid];
if (left>right){
return -1;
}
else if (num<midVal){
return binarySearch(arr,num,left,mid-1);
}
else if (num>midVal){
return binarySearch(arr,num,mid+1,right);
}else {
return mid;
}
}
}
分治算法
分治算法介绍 分治法。字面上的解释是“分而治之”,就是把一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题,直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并。这个技巧是很多高效算法的基础,如排序算法(快速排序,归并排序),傅立叶变换(快速傅立叶变换)
分治算法的基本步骤
- 1)分解:将原问题分解为若干个规模较小,相互独立,与原问题形式相同的子问题
- 2)解决:若子问题规模较小而容易被解决则直接解,否则递归地解各个子问题
- 3)合并:将各个子问题的解合并为原问题的解。
汉诺塔问题
汉诺塔的传说 汉诺塔:汉诺塔(又称河内塔)问题是源于印度一个古老传说的益智玩具。大梵天创造世界的时候做了三根金刚石柱子,在一根柱子上从下往上按照大小顺序摞着64片黄金圆盘。大梵天命令婆罗门把圆盘从下面开始按大小顺序重新摆放在另一根柱子上。并且规定,在小圆盘上不能放大圆盘,在三根柱子之间一次只能移动一个圆盘。 假如每秒钟一次,共需多长时间呢?移完这些金片需要5845.54亿年以上,太阳系的预期寿命据说也就是数百亿年。真的过了5845.54亿年,地球上的一切生命,连同梵塔、庙宇等,都早已经灰飞烟灭。
汉诺塔思路分析:
- 1)如果是有一个盘,A->C 如果我们有n >=2情况,我们总是可以看做是两个盘1.最下边的盘2.上面的盘
- 2)先把最上面的盘A->B
- 3)把最下边的盘A->C4)把B塔的所有盘从B->C
package algorithmAnalysis;
public class DivideAndConquer {
public static void main(String[] args) {
hanoi(3,'a','b','c');
}
public static void hanoi(int num,char a,char b,char c){
if (num==1){
System.out.println("第1个:"+a+"->"+c);
}
if (num>1){
hanoi(num-1,a,c,b);
System.out.println("第"+num+"个:"+a+"->"+c);
hanoi(num-1,b,a,c);
}
}
}
动态规划
动态规划算法介绍
- 1)动态规划(Dynamic Programming)算法的核心思想是:将大问题划分为小问题进行解决,从而一步步获取最优解 的处理算法
- 2)动态规划算法与分治算法类似,其基本思想也是将待求解问题分解成若干个子问题,先求解子问题,然后从这 些子问题的解得到原问题的解。
- 3)与分治法不同的是,适合于用动态规划求解的问题,经分解得到子问题往往不是互相独立的。(即下一个子阶段的求解是建立在上一个子阶段的解的基础上,进行进一步的求解)
- 4)动态规划可以通过填表的方式来逐步推进,得到最优解.
背包问题(0-1)
问题描述 背包问题:有一个背包,容量为4磅,现有如下物品
- 1)要求达到的目标为装入的背包的总价值最大,并且重量不超出
- 2)要求装入的物品不能重复
思路分析
- (1)
v[i][0]=v[0][i]=0; //表示填入表第一行和第一列是0 - (2)当w[i-1]>j时,
v[i][j]=v[i-1][j]; 当准备加入新增的商品的容量大于当前背包的容量时,就直接使用上一个单元格的装入策略 - (3)当==j>=w[i]==时:
v[i][j]=val[i-1]+v[i-1][j-w[i-1]]; 当准备加入的新增的商品的容量小于等于当前背包的容量
装入的方式:
- v[i-1][j]:就是上一个单元格的装入的最大值
- v[i-1][j-w[i-1]:装入i-1商品,到剩余空间j-w[i]的最大值
|