第十八天
线程池
有锁线程池
之前介绍了线程池如何使用,现在来自己实现一个线程池。首先考虑线程池的主要功能,如何把阻塞队列中的任务拿来给线程做?
这个怎么实现呢?难道线程里的runnable可以在运行时被替换的吗?打开threadpoolexecutor的源代码,发现其实是单次调用了取出来的runnable的run()来实现的…意思就是队列中的runnable压根就没有挂载到线程上来。
然后再考虑初始情况,0线程,新进任务进来,肯定是要新建线程的,然后开始执行run()。
新任务不断进来,直到到达核心线程数,接下来应该将任务放入阻塞队列了,这时候就要考虑了,我们的线程池中的工人必须技能执行初始情况(新建后跑进来的任务)又要能够自主取任务(从阻塞队列中尝试取出一个任务执行),到这时候突然明白了线程池为什么要使用阻塞队列了,如果阻塞队列中没有任务,那么当前线程会被阻塞,直到新任务抵达才会继续工作,这是线程池的核心,也是为什么线程池实现了节省线程创建开销的原因。
当阻塞队列满了之后,还会继续创建新线程直至最大线程数。
下面开始自己动手实现:
首先考虑最简单的方法,即大规模使用lock来实现。首先考虑给谁上锁。
我们的线程池类需要有一个int用来统计当前正在运行的线程数,我们要锁住的是这个变量,因为线程池这个工具的核心功能就是控制线程的数目,不然的话直接进来一个任务新建一个线程就好了,不需要线程池来管理。
那么既然要控制这个变量,就需要我们的控制器能够时刻都能够观察到变量的值。也就是说这个int对线程池这个类是始终可见的,到了这里可以用volatile或者AtomicInteger。然而实际上我们的控制器不仅仅要时刻观察这个变量的值,还需要根据值的大小进行管控,而管控是需要时间的,我们要求在管控的过程内需要这个变量不能发生改变。所以就要在管控过程内进行上锁,而工人类需要在改变该变量的值时上锁(主要是减,加的时候放在管控类里进行,这样可以忽略线程启动的时间,然而这样可能造成一个后果,就是主控类无法感知线程是否启动成功)。
有了锁,接下来需要考虑如何调度工人类,通过阅读线程池的功能,发现核心线程一经创建便永远也不会销毁,除非发送shutdown指令,所以我们的核心线程在取任务时选用take。而临时线程当任务队列为空时便会自动销毁,所以临时线程在取队列时可以使用poll。
接下来考虑如何shutdown,我们可以判断一个boolean从而打破循环。然后还要考虑正在被取任务阻塞的线程如何唤醒,ArrayBlockingQueue似乎并没有直接提供这种方法。在官方ThreadPoolExecutor类中它是用了一个数组盛放所有线程,然后通过这个数组管理线程关闭,我自己写的线程池不想用数组来找到线程,也就是说我的线程池中的线程对线程池类来说是不可见的,只能通过线程单向联系线程池。
所以我一开始想通过向任务队列中塞入当前线程数个空指针来实现,然而这样会抛出空指针异常,而ArrayBlockingQueue这个类又到处都是private,没法改代码。所以最好只好在任务类中添加一个属性,塞入一堆空run的任务来实现(这样就不能用runnable作为任务了)。
代码如下:
LockThreadPool 类(主控类):
package com.zht0302.MyThreadPool;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;
public class LockThreadPool
{
static final int MODE_CORE=1;
static final int MODE_TEMP=2;
static boolean guard=true;
volatile boolean endSignal=false;
int coreThreadNum=5;
int maxThreadNum=10;
int queueNum=100;
ReentrantLock numLock=new ReentrantLock();
int nowThread=0;
ArrayBlockingQueue<Task> taskQueue;
LockThreadPool(int core,int max,int queue)
{
coreThreadNum=core;
maxThreadNum=max;
queueNum=queue;
taskQueue=new ArrayBlockingQueue<Task>(queueNum);
}
private void addWorker(Task task,int mode)
{
Worker worker=new Worker(task,this,mode);
nowThread++;
}
public void execute(Task task)
{
numLock.lock();
System.out.println("nowThread:"+nowThread);
if(nowThread<coreThreadNum)
{
System.out.println("create a core thread");
addWorker(task,MODE_CORE);
numLock.unlock();
return;
}
else if(taskQueue.size()==queueNum)
{
System.out.println("queue full create a temp thread");
addWorker(task,MODE_TEMP);
numLock.unlock();
return;
}
try {
System.out.println("put a task");
taskQueue.put(task);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
finally
{
numLock.unlock();
}
}
public void shutDown()
{
endSignal=true;
//警备
if(guard)
{
Task nullTask=new Task();
nullTask.nullb=true;
taskQueue.clear();
int size=nowThread;
int pit;
int pot;
if(size<queueNum)
{
pit=size;
pot=0;
}
else
{
pit=queueNum;
pot=size-queueNum;
}
System.out.println("shutdown pit="+pit+" pot="+pot);
for(int i=0;i<pit;i++)
{
try {taskQueue.add(nullTask);}
catch(NullPointerException e)
{e.printStackTrace();}
}
for(int i=0;i<pot;i++)
{
try {
taskQueue.put(nullTask);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
System.out.println("have shut down");
}
}
Worker 类(用于执行任务):
package com.zht0302.MyThreadPool;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;
public class Worker extends Thread
{
static final int MODE_CORE=1;
static final int MODE_TEMP=2;
static AtomicInteger core=new AtomicInteger();
static AtomicInteger temp=new AtomicInteger();
Task task;
LockThreadPool threadPool;
boolean complete=false;
int mode;
Worker(Task t,LockThreadPool tp,int m)
{
task=t;
threadPool=tp;
System.out.println("new mode "+m+" thread start");
mode=m;
this.start();
}
public void run()
{
if(mode==MODE_CORE)
{
System.out.println("a mdoe "+mode+" thread core run");
core.getAndIncrement();
coreRun();
core.getAndDecrement();
}
else if(mode==MODE_TEMP)
{
System.out.println("a mdoe "+mode+" thread temp run");
temp.getAndIncrement();
tempRun();
temp.getAndDecrement();
}
threadPool.numLock.lock();
threadPool.nowThread--;
threadPool.numLock.unlock();
System.out.println("a mdoe "+mode+" thread down");
}
private void getTask()
{
try {
task=threadPool.taskQueue.take();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
private void tryTask()
{
task=threadPool.taskQueue.poll();
}
private void coreRun()
{
while(!complete)
{
getTask();
if(task.nullb==false)
task.run();
complete=threadPool.endSignal;
}
}
private void tempRun()
{
while(!complete)
{
tryTask();
if(task==null)
{
complete=threadPool.endSignal;
break;
}
else if(task.nullb==false)
{
task.run();
}
complete=threadPool.endSignal;
}
}
}
Task 任务类(run方法自行修改):
package com.zht0302.MyThreadPool;
public class Task implements Runnable
{
public boolean nullb=false;
public void run()
{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":one thread "+System.currentTimeMillis());
}
}
Worker里面用了两个原子int,只是用来显示输出用的,实际没有用到。
无锁线程池
之前实现的有锁线程池对线程数的控制十分精密,它利用锁严格控制了每一个线程的运行和销毁,而如果不使用锁的话,实际上我们用于监控线程数目的输出是不准确的。
这就需要我们动态的调控当前活动的线程数目(相当于控制器增加了延时部分)。
控制器线程池类(check用于检查预期值和真实值,需要外部类调用,或使用定时器调用):
package com.zht0302.MyThreadPool;
import java.util.ArrayList;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ControlThreadPool
{
static final int MODE_CORE=1;
static final int MODE_TEMP=2;
int ditt=2;
int coreThreadNum=5;
int maxThreadNum=10;
int maxTaskNum=100;
int commandNum=1;
AtomicInteger nowCount=new AtomicInteger();
AtomicInteger realCount=new AtomicInteger();
ArrayBlockingQueue<BeeActivity> activityQueue;
//核心,最大,最大任务
ControlThreadPool(int core,int max,int maxtask)
{
coreThreadNum=core;
maxThreadNum=max;
maxTaskNum=maxtask;
commandNum=maxTaskNum+max+max;
nowCount.set(0);
realCount.set(0);
ditt=(coreThreadNum/5+2);
activityQueue=new ArrayBlockingQueue<BeeActivity>(commandNum);
}
public void execute(BeeActivity activity)
{
int temp=nowCount.get();
if(temp<maxTaskNum)
{
hatchBee(activity);
try {
activityQueue.put(activity);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return;
}
else
{
System.out.println("queue full");
return;
}
}
private void hatchBee(BeeActivity a)
{
Bee bee=new Bee(a,this);
}
public void shutDown()
{
activityQueue.clear();
for(int i=0;i<2*maxThreadNum;i++)
{
BeeActivity dead=new BeeActivity();
dead.pheromone=BeeActivity.PHEROMNE_END;
try {
activityQueue.put(dead);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
nowCount.getAndDecrement();
System.out.println("one bee will dead!now num:"+nowCount);
}
System.out.println("queue full of dead. now "+realCount.get());
}
public void check()
{
if(activityQueue.size()==0)
{
for(int i=0;i<nowCount.get()-coreThreadNum;i++)
{
BeeActivity wit=new BeeActivity();
wit.pheromone=BeeActivity.PHEROMNE_WAIT;
try {
activityQueue.put(wit);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
int diff=realCount.get()-nowCount.get();
nowCount.getAndAdd(diff);
}
}
BeeActivity类,用于存放任务:
package com.zht0302.MyThreadPool;
public class BeeActivity implements Runnable
{
static final int PHEROMNE_WORK=0;
static final int PHEROMNE_WAIT=1;
static final int PHEROMNE_END=2;
public int pheromone=0;
@Override
public void run()
{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":one thread "+System.currentTimeMillis());
}
}
Bee类,执行类(由于我们无法准确知道线程数目,所以不需要分为核心模式和临时模式,统一循环取指令):
package com.zht0302.MyThreadPool;
public class Bee extends Thread
{
BeeActivity activity;
ControlThreadPool threadPool;
boolean complete=false;
Bee(BeeActivity a,ControlThreadPool ctp)
{
activity=a;
threadPool=ctp;
System.out.println("one bee will hatch!now num:"+threadPool.nowCount);
threadPool.nowCount.getAndIncrement();
this.start();
}
private void takeActivity()
{
BeeActivity trytest;
trytest=threadPool.activityQueue.poll();
if(trytest==null||trytest.pheromone==BeeActivity.PHEROMNE_WAIT)
{
suicide();
}
try {
activity=threadPool.activityQueue.take();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
private void suicide()
{
int nowBee=threadPool.nowCount.get();
if(nowBee>threadPool.coreThreadNum)
{
BeeActivity dead=new BeeActivity();
dead.pheromone=BeeActivity.PHEROMNE_END;
threadPool.activityQueue.offer(dead);
threadPool.nowCount.getAndDecrement();
System.out.println("one bee will dead!now num:"+threadPool.nowCount);
}
}
public void run()
{
threadPool.realCount.getAndIncrement();
while(activity!=null)
{
if(activity.pheromone==BeeActivity.PHEROMNE_WORK)
{
activity.run();
takeActivity();
}
else if(activity.pheromone==BeeActivity.PHEROMNE_END)
{
break;
}
else if(activity.pheromone==BeeActivity.PHEROMNE_WAIT)
{
takeActivity();
}
}
threadPool.realCount.getAndDecrement();
}
}
用于测试的类:
package com.zht0302.MyThreadPool;
import java.util.concurrent.ThreadPoolExecutor;
public class MyThreadPoolTest
{
//(核心数,最大数,队列长度)
LockThreadPool threadPool=new LockThreadPool(10, 100, 10);
//(核心,最大,最大任务)
ControlThreadPool beePool=new ControlThreadPool(100,200,200);
private void lockTest()
{
for(int i=0;i<5;i++)
{
Task t=new Task();
threadPool.execute(t);
//System.out.println(i);
}
try {
Thread.sleep(1000);
} catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
for(int i=0;i<1;i++)
{
Task t=new Task();
threadPool.execute(t);
//System.out.println(i);
}
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
threadPool.shutDown();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("count: core:"+Worker.core.get()+" temp:"+Worker.temp.get());
}
private void controlTest()
{
for(int i=0;i<100;i++)
{
BeeActivity a=new BeeActivity();
beePool.execute(a);
//System.out.println(i);
}
try {
Thread.sleep(1000);
} catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
for(int i=0;i<200;i++)
{
BeeActivity a=new BeeActivity();
beePool.execute(a);
//System.out.println(i);
}
for(int i=0;i<10;i++)
{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("nowcount:"+beePool.nowCount.get()+" realcount:"+beePool.realCount.get());
beePool.check();
}
beePool.shutDown();
try {
Thread.sleep(1500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("count: core:"+Worker.core.get()+" temp:"+Worker.temp.get());
}
public void test()
{
//lockTest();
controlTest();
}
}
第十九天
队列
今天来实现一个双端队列。首先考虑队列的头尾操作模式有什么优势,为什么要这样设计?
如果我们使用跟列表一样的方法来实现队列的话,那么完全可以在其中插入删除,没必要规定非在首尾操作。首尾操作既然牺牲了功能,那么就在性能上找回来,考虑到内存中地址都是一维存储的,只操作首尾的话,实际上保护住了队列中的数据段,只需要定位一个首地址,并给出一个长度(或给出尾地址)就可以确定队列在内存中的位置。
而首尾添加删除,无非就是更改首地址和尾地址,如果我们能够直接操作计算机内存的话,仅此就够了。然而实际上我们无法跳过操作系统直接操作内存,所以需要初始时给定数组长度,而在此过程中首地址移位会造成前排的空缺,一般使用循环队列来解决。
双端队列类:
package com.zht0210.DataStructure;
//双端队列
public class MyQueue<T>
{
public static int initLength=10;
private int activitySize=0;
private int size=initLength;
private int growLength=initLength;
private boolean loop=false; //loop时maxsize=activitysize,非loop时,可能会有空位
private Object[] value=new Object[initLength];
private int front=0;
private int rear=0;
public void addFront(T t)//先计算,再存储
{
if(activitySize+1>size)
{
grow();
}
if(loop)
{
front--;
value[front]=t;
}
else
{
if(front>0)
{
front--;
value[front]=t;
}
else
{
front=size-1;
value[front]=t;
loop=true;
}
}
activitySize++;
}
public void removeFront()//先删除,再计算
{
if(activitySize==0)
{
System.out.println("no value");
return;
}
else
{
if(activitySize-1<size/2&&activitySize>initLength)
{
cut();
}
if(loop)
{
if(front+1==value.length)
{
value[front]=null;
front=0;
loop=false;
}
else
{
value[front]=null;
front++;
}
}
else
{
value[front]=null;
front++;
}
activitySize--;
}
}
public void addRear(T t)//先存储,再计算
{
if(activitySize+1>size)
{
grow();
}
//if(loop)
//{
value[rear]=t;
rear++;
//}
//else
//{
// value[rear]=t;
// rear++;
//}
activitySize++;
}
public void removeRear()//先计算,再删除
{
if(activitySize==0)
{
System.out.println("no value");
return;
}
else
{
if(activitySize-1<size/2&&activitySize>initLength)
{
cut();
}
if(loop)
{
if(rear==0)
{
rear=value.length-1;
value[rear]=null;
loop=false;
}
else
{
rear--;
value[rear]=null;
}
}
else
{
rear--;
value[rear]=null;
}
}
activitySize--;
}
public T get(int i)
{
if(i>activitySize||i<0)
{
System.out.println("下标不合法");
return null;
}
return (T) value[regularizationInt(i)];
}
public int size()
{
return activitySize;
}
private int regularizationInt(int si)//为了增加性能,并没有调用
{
//(front+si)%value.length//loop时等于 非loop时为0
return (front+si)%value.length;
}
private void grow()//保证loop时maxsize=activitysize,非loop时,可能会有空位
{
//System.out.println("grow");
Object[] temp=new Object[calculationGrow()];
if(loop)
{
int len=value.length-front;
System.arraycopy(value, front, temp, 0, len);
//System.out.println("cp "+(len));
System.arraycopy(value, 0, temp, len, rear);
//System.out.println("cp "+(rear));
}
else
{
System.arraycopy(value, front, temp, 0, activitySize);
//System.out.println("cp "+(activitySize));
}
loop=false;
value=temp;
front=0;
rear=activitySize;
size=value.length;
//show();
}
private void cut()
{
Object[] temp=new Object[activitySize];
if(loop)
{
int len=value.length-1-front;
System.arraycopy(value, front, temp, 0, len);
System.arraycopy(value, 0, temp, len, rear);
}
else
{
System.arraycopy(value, front, temp, 0, activitySize);
}
loop=false;
value=temp;
front=0;
rear=activitySize;
size=value.length;
}
private int calculationGrow()
{
return size*2;
}
public void show()
{
System.out.println("activity size:"+activitySize);
System.out.println("szie:"+size);
System.out.println("front:"+front);
System.out.println("rear:"+rear);
for(int i=0;i<this.activitySize;i++)
{
System.out.println(this.get(i));
}
// for(int i=0;i<this.size;i++)
// {
// System.out.println(value[i]);
// }
}
}
这里按道理应该有一个int负责存储当前循环长度,但是我定义了一个规则(循环时长度=value.length,非循环时,才可能会有空位,但这时也用不到需循环长度),在此规则下,需要用到这个长度的地方可以用value.length代替,所以就没有定义。
四则运算
将四则运算String转换成数值运算。
这里最主要需要考虑优先级的问题,但是我们实际生活中定义的优先级并不能很好的移植到计算机逻辑系统中来,所以有一位大神发明了后缀运算方式,只要先将四则运算表达式转换成后缀表达式,再对后缀表达式进行操作就可以了。
而操作的方法则是利用一个运算符栈,进行优先级判断。
package com.zht0219.Operations;
import java.util.HashMap;
import java.util.Stack;
public class syms
{
private String value;
private Stack<Character> postfixStack=new Stack<Character>();
static final private HashMap<Character, Integer> priority= new HashMap<Character,Integer>(){};
syms(String s)
{
value= s;
set();
change2postfix();
}
private void set()
{
priority.put('(',0 );
priority.put(')',0 );
priority.put('*',2 );
priority.put('/',2 );
priority.put('%',2 );
priority.put('+',1);
priority.put('-',1 );
}
private void change2postfix()
{
Stack<Character> tempStack=new Stack<Character>();
for(int i=0;i<value.length();i++)
{
char c=value.charAt(i);
if(c>=48&&c<=57)
postfixStack.push(c);
else if(c=='(')
{
tempStack.push(c);
}
else if(c==')')
{
while(true)
{
char tempc=tempStack.pop();
if(tempc=='(')
{
break;
}
postfixStack.push(tempc);
}
}
else if (priority.get(c)!=null)
{
if(tempStack.empty())
tempStack.push(c);
else if(priority.get(c)>priority.get(tempStack.get(tempStack.size()-1)))
{
tempStack.push(c);
}
else
{
while(!tempStack.empty())
{
postfixStack.push(tempStack.pop());
}
tempStack.push(c);
}
}
showStack(tempStack);
postfix();
}
while(!tempStack.empty())
{
postfixStack.push(tempStack.pop());
}
showStack(tempStack);
postfix();
}
public double cumValue()
{
double op1=0;
double op2=0;
Stack<Double> tempStack=new Stack<Double>();
for(int i=0;i<postfixStack.size();i++)
{
char c=postfixStack.get(i);
double d=0;
if(c>=48&&c<=57)
{
d=(double)(c-48);
}
else
{
op1=tempStack.pop();
op2=tempStack.pop();
if(c=='+')
{
d=op1+op2;
}
else if(c=='-')
{
d=op2-op1;
}
else if(c=='/')
{
d=op2/op1;
}
else if(c=='*')
{
d=op2*op1;
}
else if(c=='%')
{
d=op2%op1;
}
}
tempStack.push(d);
}
if(tempStack.size()!=1)
{
System.out.println("erro:size="+tempStack.size());
}
return tempStack.pop();
}
public String getValue()
{
return value;
}
public void change(String s)
{
value=s;
}
private void showStack(Stack stack)
{
for(int i=0;i<stack.size();i++)
{
System.out.print(stack.get(i));
System.out.print(" ");
}
System.out.print("\n");
}
public void postfix()
{
System.out.print("postfix:");
showStack(postfixStack);
}
}
字符串匹配
暴力匹配
就是很简单的挨个遍历S再遍历T。
暴力匹配函数:
private ArrayList<Integer> strMatch()
{
int anchor=0;
ArrayList<Integer> list=new ArrayList<Integer>();
for(int i=0;i<S.length()-T.length();i++)
{
for(int j=0;j<T.length();j++)
{
if(S.charAt(i+j)==T.charAt(j))
{
anchor++;
}
else
{
anchor=0;
break;
}
}
if(anchor==T.length())
{
list.add(i);
}
}
return list;
}
KMP匹配
在暴力匹配时,有一些判断是重复的,考虑上一次匹配的长度i,即已经判断过的信息有时候不需要再次判断,所以下一次要匹配的初始点可以不用+1,而是+n,最理想的情况是+i。
具体算法可以查阅KMP资料,但我觉得其实没什么大用,所以不详细研究了,就只是实现代码当做练习。
另外正经的KMP算法是i和j一起动,但我比较习惯i+j和j,所以使用了自己定义的next向量,并使用按照自己习惯改的KMP匹配。
匹配类
package com.zht0306.StringAlgorithm;
import java.util.ArrayList;
import java.util.Random;
public class StringTest
{
String T=new String();
String S =new String();
ArrayList<Integer> ll=new ArrayList<Integer>();
int[] sNext;
public void test()
{
//S="ababababababccc";
//T="abcc";
randomString(100000,100,0.5);
//S="aaaabbaaaaaxde";
//T="aaaaax";
//System.out.println(S);
System.out.println(T);
long time,time1,time2;
sNext=chrNext(T);
time1=System.nanoTime();
// for(int i=0;i<sNext.length;i++)
// {
// System.out.println(sNext[i]);
// }
ll=KMPMatch();
time2=System.nanoTime();
time=time2-time1;
//show(ll);
System.out.println(ll.size()+" time"+time);
System.out.println("-------------");
ll.clear();
time1=System.nanoTime();
ll=strMatch();
time2=System.nanoTime();
time=time2-time1;
System.out.println(ll.size()+" time"+time);
//show(ll);
}
private void randomString(int s,int t,double rat)
{
Random r=new Random();
char[] charst=new char[t];
char[] charss=new char[s];
for(int i=0;i<s;i++)
{
charss[i]=(char)('a'+(char)r.nextInt(26));
}
for(int j=0;j<t;j++)
{
charst[j]=(char)('a'+(char)r.nextInt(26));
}
int n=s/t;
n=(int) (n*rat);
int d=s/n;
for(int i=0;i<n;i++)
{
int b=r.nextInt(1+(d-t)/4);
for(int j=0;j<t;j++)
{
charss[i*d+j+b]=charst[j];
}
}
S=String.valueOf(charss);
T=String.valueOf(charst);
}
private void show(ArrayList<Integer> ll)
{
for(int i=0;i<ll.size();i++)
{
System.out.println(ll.get(i));
}
}
private ArrayList<Integer> strMatch()
{
int anchor=0;
ArrayList<Integer> list=new ArrayList<Integer>();
for(int i=0;i<S.length()-T.length();i++)
{
for(int j=0;j<T.length();j++)
{
if(S.charAt(i+j)==T.charAt(j))
{
anchor++;
}
else
{
anchor=0;
break;
}
}
if(anchor==T.length())
{
list.add(i);
}
}
return list;
}
private int[] chrNext(String s)
{
int[] temp=new int[s.length()];
for(int n=1;n<s.length();n++)
{
int max=0;
int t;
for(int i=1;i<n;i++)
{
t=i;
for(int j=0;j<i;j++)
{
//System.out.println("n:"+n+" i:"+i+" j:"+j+" char:"+s.charAt(n)+" char-j:"+s.charAt(j)+" char-n-i+j:"+s.charAt(n-i+j));
if(s.charAt(j)!=s.charAt(n-i+j))
{
t=0;
break;
}
}
max=max>t?max:t;
}
temp[n]=n-max;
}
temp[0]=1;
for(int i=1;i<temp.length;i++)
{
if(temp[i]==1)
{
if(s.charAt(i)==s.charAt(0))
{
temp[i]=i;
}
}
}
return temp;
}
private ArrayList<Integer> KMPMatch()
{
int anchor=0;
ArrayList<Integer> list=new ArrayList<Integer>();
for(int i=0;i<S.length()-T.length();)
{
for(int j=0;j<T.length();j++)
{
if(S.charAt(i+j)==T.charAt(j))
{
anchor++;
}
else
{
anchor=0;
i=i+sNext[j];
break;
}
}
if(anchor==T.length())
{
list.add(i);
i++;
}
}
return list;
}
}
结果:
500 time17152600
500 time22989500
KMP确实比暴力匹配要快一点,但是如果加上计算next向量就不快了(我的计算next向量方法比较冗余,看书上写的很简洁,但是因为是练习,所以尽可能保证逻辑完整,而不是保证简洁)。
二进制匹配
考虑到在计算机中,一次if(a==b)需要执行一次减法再执行一次0位判断,即先a-b,如果结果为0,则认为相等,同理><也类似。
而在string判断中大量使用了if判断,主要是第二遍循环中执行次数太多。
而如果一串字符串跟另一串字符串完全相等的话,那么无论对它们进行任何操作,都会得到相等的结果,所以,可以先整体做判断,然后逐步缩小判断范围,到最后再进行完全匹配判断。
我这里先用了一个和值判断,又用了一个奇偶正负加法判断。
private ArrayList<Integer> binaryMatch()
{
int target=0;
int[] one=new int[S.length()];
ArrayList<Integer> list=new ArrayList<Integer>();
for(int i=0;i<T.length();i++)
{
target=target+(int)T.charAt(i);
}
int sor=0;
for(int i=0;i<T.length();i++)
{
sor=sor+(int)S.charAt(i);
}
int oneNum=0;
for(int i=0;i<S.length()-T.length();i++)
{
if(sor==target)
{
one[oneNum++]=i;
}
else
{
sor=sor-(int)S.charAt(i)+(int)S.charAt(i+T.length());
}
}
int[] two=new int[oneNum];
target=0;
for(int i=0;i<T.length();i++)
{
target=target+((i%2==0)?1:-1)*(int)T.charAt(i);
}
sor=0;
for(int i=0;i<T.length();i++)
{
sor=sor+((i%2==0)?1:-1)*(int)T.charAt(i);
}
int twoNum=0;
for(int i=0;i<two.length;i++)
{
if(sor==target)
{
two[twoNum++]=one[i];
}
else
{
sor=sor-((i%2==0)?1:-1)*(int)S.charAt(i)+(((i+T.length())%2==0)?1:-1)*(int)S.charAt(i+T.length());
}
}
int anchor=0;
for(int i=0;i<twoNum;i++)
{
for(int j=0;j<T.length();j++)
{
if(S.charAt(two[i]+j)==T.charAt(j))
{
anchor++;
}
else
{
anchor=0;
break;
}
}
if(anchor==T.length())
{
list.add(i);
}
}
return list;
}
不过,实际运行速度还不如暴力匹配,因为运算中大量使用cast,所以耽误了很多时间,如果直接把二进制拿来计算,速度可能会快很多。
其次,现在匹配的字符串限定在26个,基本上第一位就直二十六分之一pass掉了,能进入之后循环的很少很少,所以暴力匹配并不会耗费太多时间在循环中。
第二十天
树
树状结构是计算机存储结构中的一种常用类别,它类似于链表结构,只不过每个节点都存储了n个指向其他节点的指针,像分叉生长的树一样,故名树状结构。
考虑为什么要设计树状结构?
首先跟数组和链表比起来树状结构存储也不占优势,取元素也不占优势,所以单独拿树状结构出来存储,是一种十分愚蠢的行为。
例如,我在五子棋项目中,AI的推理过程每次落子都将产生N种候选棋局,每种棋局又可以落子再产生N*N种候选棋局。这个结构看起来很像树状结构,用树状结构来存储是不是很好呢?
然而,该种结构完全可以用一个数组来存储,数组的长度=N+N ^2+N ^3+N ^4…,或者使用2维数组,第一个数组盛放第k步,第二个数组盛放第k步中第m种棋局。而在读取时,只需要按照对应的数学关系求解下标即可。
但是这样使用有一个前提,那就是我们确切知道要存储的棋局放在哪里,例如第k步第m种。但假如在某个问题中,我们不知道要add进来的数据具体存到哪呢?
如果该存储结构对数据没有顺序要求,那加到哪都行,但如果是一个大小排序的数据结构,例如一个列表里存储了0,4,5,6,7,10,12.那么新加进来一个8,我们需要把8跟每一个元素做比较,然后将它插入到合适的位置。
而树状结构本身提供了一种约束,即高位节点和低位节点之间的顺序不可打乱(类似于单向链表)。同时如果兄弟间也有顺序的话,还可以约束左右兄弟顺序不可打乱。
有了这样的约束关系,树状结构才有了灵魂,才可以发挥它自身的作用。
思考:在高维空间中,如果有更多的约束关系,会有高维树结构吗?
二叉树
二叉树是树状结构中最简单的结构,但同时也是应用最广泛的,因为它有很多性质可以直接使用,方便算法编写。
一个二叉树,对每个节点都要求,该节点只能有两个分支,一般称为首孩子和右兄弟(为什么这么叫,不叫大孩子和小孩子,是因为多叉树转换为二叉树时,会把下一个兄弟编为孩子)。
二叉树的遍历有很多种方法(前序遍历,后序遍历,中序遍历,层序遍历)。无论哪种方法,一般都使用递归实现。
但是我很讨厌递归,且在get方法时,递归函数和get函数还要分开,所以没有用递归实现。
BinaryTree结构(可以创建,get):
package com.zht0210.DataStructure;
import java.util.ArrayList;
import java.util.Stack;
public class BinaryTree<T>
{
class BinaryTreeNode<T>
{
int ID=-1;
BinaryTreeNode<T> firstChild;
BinaryTreeNode<T> rightBrother;
T data;
public void setID(int n)
{
ID=n;
}
}
BinaryTreeNode<T> root=new BinaryTreeNode<T>();
private int treeSize=0;
public void create(T[] vector)
{
BinaryTreeNode<T> recursionPoint=root;
Stack<BinaryTreeNode<T>> breakList=new Stack<BinaryTreeNode<T>>();
boolean goif=false;
recursionPoint.data=vector[0];
breakList.push(recursionPoint);
treeSize++;
for(int i=0;i<vector.length-1;)
{
if(!goif)
{
if(vector[++i]!=null)
{
BinaryTreeNode<T> node=new BinaryTreeNode<T>();
breakList.push(recursionPoint);
recursionPoint.firstChild=node;
recursionPoint=node;
//System.out.println("i:"+i+" recursion:"+recursionPoint.hashCode()+" data:"+vector[i]);
//System.out.println("push+1");
recursionPoint.data=vector[i];
treeSize++;
continue;
}
else
{
//System.out.println("i:"+i+" null");
}
}
else
{
goif=false;
}
if(vector[++i]!=null)
{
BinaryTreeNode<T> node=new BinaryTreeNode<T>();
recursionPoint.rightBrother=node;
recursionPoint=node;
//System.out.println("i:"+i+" recursion:"+recursionPoint.hashCode()+" data:"+vector[i]);
recursionPoint.data=vector[i];
treeSize++;
}
else
{
//System.out.println("i:"+i+" null");
//System.out.println("pop+1");
recursionPoint=breakList.pop();
goif=true;
}
}
}
public T get(int n)
{
if(n>=treeSize)
{
System.out.println("out of range!");
return null;
}
BinaryTreeNode<T> recursionPoint=root;
Stack<BinaryTreeNode<T>> breakList=new Stack<BinaryTreeNode<T>>();
boolean goif=false;
for(int i=0;i<n;)
{
//System.out.println("i:"+i+" "+recursionPoint.hashCode());
if(!goif)
{
if(recursionPoint.firstChild!=null)
{
breakList.push(recursionPoint);
//System.out.println(recursionPoint.hashCode());
recursionPoint=recursionPoint.firstChild;
//System.out.println(recursionPoint.hashCode());
i++;
continue;
}
}
else
{
goif=false;
}
if(recursionPoint.rightBrother!=null)
{
recursionPoint=recursionPoint.rightBrother;
i++;
}
else
{
recursionPoint=breakList.pop();
goif=true;
}
}
return (T)recursionPoint.data;
}
public int size()
{
return treeSize;
}
public void show()
{
for(int i=0;i<treeSize;i++)
{
System.out.println(this.get(i));
}
}
public void testGet()
{
System.out.println(root.firstChild.firstChild.firstChild.data);
//System.out.println(root.firstChild.firstChild.data);
//System.out.println(root.firstChild.data);
//System.out.println(root.rightBrother.data);
//System.out.println(root.data);
}
public void testSet(T[] vector)
{
BinaryTreeNode<T> node0=new BinaryTreeNode<T>();
//System.out.println(node0.hashCode());
node0.data=vector[0];
treeSize++;
root=node0;
BinaryTreeNode<T> node1=new BinaryTreeNode<T>();
//System.out.println(node1.hashCode());
node1.data=vector[1];
treeSize++;
root.firstChild=node1;
BinaryTreeNode<T> node2=new BinaryTreeNode<T>();
//System.out.println(node2.hashCode());
node2.data=vector[2];
treeSize++;
root.rightBrother=node2;
BinaryTreeNode<T> node3=new BinaryTreeNode<T>();
//System.out.println(node3.hashCode());
node3.data=vector[3];
treeSize++;
node1.firstChild=node3;
BinaryTreeNode<T> node4=new BinaryTreeNode<T>();
//System.out.println(node3.hashCode());
node4.data=vector[4];
treeSize++;
node1.rightBrother=node4;
BinaryTreeNode<T> node5=new BinaryTreeNode<T>();
//System.out.println(node3.hashCode());
node5.data=vector[5];
treeSize++;
node2.rightBrother=node5;
}
}
写完之后看了网上的代码,发现遍历的时候有一个boolean量goif其实是没有用的,去掉之后代码会简洁很多,因为在二叉树中,每一次记录入栈的节点,都是已经搜寻过首孩子的节点,所以没必要再判断,在弹栈的时候,直接赋予右兄弟即可。不过在多叉树中,或许需要一个int,用来计数。
网络代码:
public void Order(){
Stack<BinaryTreeNode<T>> sk=new Stack<BinaryTreeNode<T>>();
BinaryTreeNode<T> point=root;
while(!sk.isEmpty()||point!=null){
if(point!=null){
System.out.print("<<<");
System.out.print(point.data);
sk.push(point);
point=point.firstChild;
}
else{
point=sk.pop();
point=point.rightBrother;
}
}
}
霍夫曼编码
霍夫曼编码是为了压缩数据,它的核心奥义是,用的多的数据转换为低位的二进制数据,用得少的数据转换为高位的二进制数据。
比如说[A,A,A,B,C,C,D,A,A,A,E,F,G]这么一串数据,其中共出现7个字母,如果采用定长存储,至少需要3位的bit存储每一种字母,如下所示[000,000,000,001,010,010,011,000,000,000,100,101,110]。
但是霍夫曼注意到这其中A出现了很多次,如果让A编为0,C编为1,其他的向后编码,那不就可以省很多空间吗
类似这种[0,0,0,x,1,1,x,0,0,0,x,x,x],但是实际上在串行传输中,是没有逗号的,直接把0和1都占了,其他数据是无法识别到底多长的。所以编A为0,其他字母以1开头然后偶依次向后编码。
这样就能节约很多空间,但是我们要怎么计算出数据的编码来呢?
考虑到越多出现的数据,就越应该占用较短的编码。如果不考虑串行截取位数的话,只需要对数据频率进行一个排序即可,然后将0,1,00,01这样的编码依次对应到排序后的数据种类上。
而要考虑串行位数的问题,那就跟字符串匹配一样,不能出现相同的编码。
其实有很多种方法可以做到,核心思想就是我们要找到一条路径,在路径的终端获取编码后,这条路就不能再继续向下走了,如果继续向下走就会出现重复。且越早获取的编码,频率权值越大。
这样刚好跟树状结构特性相符合,树状结构具有严格的上下层关系,可以用来做频率权值的排序,树状结构的叶子节点无法生出新节点。
所以霍夫曼编码是已知全部叶子节点,按从大到小层级顺序安排叶子节点进入树。
是一个逆生长的树状结构。
代码如下:
package com.zht0311.TreeAlgorithm;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Stack;
public class Huffman
{
boolean live=false;
class Nd
{
Nd bit0;
Nd bit1;
int weight=0;
int index=-1;
}
Comparator<Nd> compa=new Comparator<Nd>()
{
public int compare(Nd n1, Nd n2)
{
return ((Integer)(n1.weight)).compareTo((Integer)(n2.weight));
}
};
ArrayList<Nd> nowNd=new ArrayList<Nd>();
Huffman(int[] weightList)
{
live=true;
if(weightList.length<3)
{
System.out.println("can not huffman");
return;
}
for(int i=0;i<weightList.length;++i)
{
Nd n=new Nd();
n.weight=weightList[i];
n.index=i;
nowNd.add(n);
}
}
public void encode()
{
int num=nowNd.size();
for(int i=0;i<num-2;++i)
{
once();
}
for(int i=0;i<nowNd.size();++i)
{
System.out.println(nowNd.get(i).weight);
}
Nd root=new Nd();
root.bit0=nowNd.get(0);
root.bit1=nowNd.get(1);
String[] codeArray=order(root,num);
for(int i=0;i<codeArray.length;++i)
{
System.out.println(codeArray[i]);
}
}
private void once()
{
nowNd.sort(compa);
Nd n=new Nd();
n.bit0=nowNd.get(0);
n.bit1=nowNd.get(1);
n.weight=n.bit0.weight+n.bit1.weight;
nowNd.remove(0);
nowNd.remove(0);
nowNd.add(n);
}
private String[] order(Nd r,int num)
{
String[] codeArray=new String[num];
Stack<Nd> nds=new Stack();
Stack<String> codes=new Stack<String>();
Nd point;
point=r;
String code="";
nds.push(point);
codes.push(code);
int n=num;
while(n>0)
{
//System.out.println("n:"+n+" weight:"+point.weight);
int d;
if(point.index!=-1)
{
d=point.index;
codeArray[d]=code;
System.out.println("bit1 d:"+d+" code:"+code);
n--;
code=codes.pop();
point=nds.pop();
code=code+"1";
point=point.bit1;
continue;
}
if(point.bit0.index==-1)
{
codes.push(code);
code=code+"0";
nds.push(point);
//System.out.println("push "+code+" in:"+point.weight);
point=point.bit0;
}
else
{
code=code+"0";
d=point.bit0.index;
codeArray[d]=code;
System.out.println("bit0 d:"+d+" code:"+code);
code = code.substring(0,code.length() - 1);
n--;
if(point.bit1.index==-1)
{
code=code+"1";
point=point.bit1;
}
else
{
code=code+"1";
d=point.bit1.index;
codeArray[d]=code;
System.out.println("bit1 d:"+d+" code:"+code);
code = code.substring(0,code.length() - 1);
n--;
code=codes.pop();
point=nds.pop();
//System.out.println("pop to:"+point.weight);
code=code+"1";
point=point.bit1;
}
}
}
return codeArray;
}
}
这里本来打算用之前网络学来的遍历代码,结果发现直接用不了,改来改去改的有点呆,还不如直接用我原来的。(取巧代码虽然看起来很灵活,但是一旦扩充功能就不能使用了,是一种很典型的面向过程编程的思路)。
第二十一天
图
图是一种复杂的数据结构,同样的,它和树一样不适合用来纯粹的存储数据,并且我个人觉得它其实在约束性方面也不如树状结构,做个图出来纯粹是为了迎合人类的逻辑方式。
包括图的许许多多的定义,以及边顶点的各种表述方式,其实都可以以更有效率的方式存储,只不过人类看起来会比较费劲。而把图表示成边和顶点的关系,并绘制出来(或任由人脑想象)更适合人类的视觉模型,也更有利于人类思考。
路径dijkstra算法
在图中的寻路算法,其实都是利用了逐步搜寻的原理,即先找出某些顶点距离远点最近的距离,再在下一次搜索时找出距离已经搜索到的顶点最近的距离。
然而这种算法能够运行的前提是,距离之间的叠加是线性的,不过目前的图基本都是线性的,可以直接使用简单算法进行处理。
dijkstra类
package com.zht0311.GraphAlgorithm;
import java.util.ArrayList;
public class Dijkstra
{
int vex;
int[][] adjMat;
int start,target;
Dijkstra(int[][] adj,int v)
{
adjMat=new int[v][v];
vex=v;
for(int i=0;i<vex;i++)
{
int[] temp=adj[i];
for(int j=0;j<vex;j++)
{
adjMat[i][j]=temp[j];
}
}
}
public ArrayList<Integer> findWay(int s,int t)
{
start=s;
target=t;
int[] backward=new int[vex];
for(int i=0;i<vex;i++)
{
backward[i]=-1;
}
backward[0]=0;
int[] mins=new int[vex];
for(int i=0;i<vex;i++)
{
mins[i]=GraphAlgorithmTest.M;
}
mins[0]=0;
int[] fina=new int[vex];
for(int i=0;i<vex;i++)
{
fina[i]=0;
}
int point=s;
int next=s;
ArrayList<Integer> way=new ArrayList<Integer>();
int count=1;
fina[point]=1;
int minIndex=point;
int min=GraphAlgorithmTest.M;
//初始化
for(int it=0;it<vex;it++)
{
if(it!=point)
{
mins[it]=adjMat[point][it];
if(mins[it]<min)
{
min=mins[it];
minIndex=it;
}
}
}
backward[minIndex]=start;
System.out.println("fina["+minIndex+"]");
count++;
find:
while(count<=vex)
{
minIndex=point;
min=GraphAlgorithmTest.M;
System.out.println("a loop: next:"+next+" point:"+point);
//得到point和next
for(int it=0;it<vex;it++)
{
if(adjMat[point][it]<min&&fina[it]==0)
{
minIndex=it;
min=adjMat[point][it];
}
}
System.out.println("minIndex:"+minIndex);
next=minIndex;
//检查下一个是否已经测算
boolean voi=true;
while(!findFina(fina,next)&&voi)
{
System.out.println("next:"+next);
if(next==fina.length-1)
{
next=0;
}
else
{
next++;
}
voi=false;
for(int it=0;it<vex;it++)
{
if(adjMat[next][it]<GraphAlgorithmTest.M)
{
if(fina[it]==1&&next!=it)
{
point=it;
voi=true;
break;
}
}
}
}
System.out.println("test loop: next:"+next+" point:"+point);
point=next;
for(int it=0;it<vex;it++)
{
if(min+adjMat[point][it]<mins[it]&&fina[it]==0&&it!=point)
{
mins[it]=min+min+adjMat[point][it];
backward[it]=point;
System.out.println("backward["+it+"]=:"+point);
}
}
fina[point]=1;
if(point==target)
{break;}
count++;
System.out.println("end loop fina["+point+"]");
}
for(int i=0;i<backward.length;i++)
{
System.out.print(backward[i]+" ");
}
point=target;
while(point!=start)
{
way.add(0, point);
point=backward[point];
}
return way;
}
private boolean findFina(int[] fina,int next)
{
for(int it=0;it<fina.length;it++)
{
if(fina[it]==1&&next==it)
{
return false;
}
}
return true;
}
}
测试类:
package com.zht0311.GraphAlgorithm;
import java.util.ArrayList;
public class GraphAlgorithmTest
{
int vex=100;
int[][] adjMat;
static final int M=99999;
Dijkstra dij;
public void test()
{// 0,1,2,3,4,5,6,7,8
int[] ele= {0,1,5,0,0,0,0,0,0,
1,0,3,7,5,0,0,0,0,
5,3,0,0,1,7,0,0,0,
0,7,0,0,2,0,3,0,0,
0,5,1,2,0,3,6,9,0,
0,0,7,0,3,0,0,5,0,
0,0,0,3,6,0,0,2,7,
0,0,0,0,9,5,2,0,4,
0,0,0,0,0,0,7,4,0};
vex=(int)(Math.sqrt((double)ele.length));
adjMat=new int[vex][vex];
setMat(ele);
//showMat(adjMat);
dij=new Dijkstra(adjMat,vex);
ArrayList<Integer> result=dij.findWay(0, 8);
System.out.println("result:");
for(int i=0;i<result.size();i++)
{
System.out.print("->"+result.get(i));
}
}
private void showMat(int[][] mat)
{
for(int i=0;i<adjMat.length;i++)
{
int[] temp=adjMat[i];
for(int j=0;j<temp.length;++j)
{
System.out.println(temp[j]);
}
}
}
private void setMat(int[] ele)
{
int n=0;
for(int i=0;i<adjMat.length;i++)
{
int[] temp=adjMat[i];
for(int j=0;j<temp.length;++j)
{
temp[j]=ele[n];
n++;
}
}
if(n<vex*vex)
{
System.out.println("not full");
}
for(int i=0;i<adjMat.length;i++)
{
int[] temp=adjMat[i];
for(int j=0;j<temp.length;++j)
{
if(temp[j]==0&&i!=j)
temp[j]=M;
}
}
}
}
Floyd算法
floyd算法本质上跟dijkstra算法类似,都是利用了已求得最佳路径信息,只不过floyd面向全局,每次对所有节点进行更新,而一旦建立完成,只需要查表即可寻找路径。
另外floyd利用了两点之间直线距离最短作为表格的初始化数据,加快了计算速度,然而由于循环没有实际上提前退出方式,所以没有用(提前退出并不能保证每个路径都对了)。
而该矩阵初始化时必须满足对角线上的元素为下标位置。
floyd算法
package com.zht0311.GraphAlgorithm;
import java.util.ArrayList;
public class Floyd
{
int[][] passward;
int[][] adjMat;
int vex;
int start,target;
boolean set=false;
Floyd(int[][] adj,int v)
{
adjMat=new int[v][v];
passward=new int[v][v];
vex=v;
for(int i=0;i<vex;i++)
{
int[] temp=adj[i];
for(int j=0;j<vex;j++)
{
adjMat[i][j]=temp[j];
}
}
for(int i=0;i<vex;i++)
{
int[] temp=new int[vex];
for(int j=0;j<vex;j++)
{
temp[j]=j;
//temp[j]=j+i;
}
// for(int j=0;j<i;j++)
// {
// int in=temp.length-j-1;
// int d=vex;
// temp[in]=temp[in]-d;
// }
passward[i]=temp;
}
showWard();
}
private void showWard()
{
for(int i=0;i<vex;i++)
{
for(int j=0;j<vex;j++)
{
System.out.print(" "+passward[i][j]);
}
System.out.println();
}
}
public void setP()
{
int pivot;
int s;
int t;
for(int i=0;i<vex;i++)
{
pivot=i;
for(int n=0;n<vex;n++)
{
s=n;
for(int m=0;m<vex;m++)
{
t=m;
if(adjMat[s][i]+adjMat[i][t]<adjMat[s][t])
{
passward[s][t]=passward[s][i];
adjMat[s][t]=adjMat[s][i]+adjMat[i][t];
}
}
}
}
showWard();
}
public ArrayList<Integer> findWay(int s,int t)
{
start=s;
target=t;
ArrayList<Integer> way=new ArrayList<Integer>();
int point=start;
while(point!=target)
{
way.add(point);
point=passward[point][target];
}
way.add(target);
return way;
}
}
总结:
对任意一种搜索方法来说,任意两点之间的距离,可能组合为k*(k-1)/2 (k=N-2),实际上在O中是k^2。
对全部两点来说,O是k^3(但实际组合远小于这个数)。