代码行统计 ( Mon, 20 Apr 2009 13:24:09 +0800 )
Description: #find . -name *.c |xargs wc -l
802 ./lcec/src/Slcec_conf.c
2380 ./lcec/src/Slcec_view.c
1435 ./lcec/src/Slcec_ctl.c
……
84876 total
指针和引用 ( Wed, 8 Apr 2009 16:18:29 +0800 )
Description: int (*pa)[3];
int b[2][3];
pa=b;
pa是一个指向一个具有3个int型元素的一位数组的指针
如果不加括号,可以理解为int* pa[3];
——————-
二级指针和指针数组:
char *week[]={“”,”Mon”,”Tue”,”Wed”,”Thu”,”Fri”,”Sat”,”Sun”};
void main(int argc,char **argv){
cout<<week[2]<<endl;
}
或
void main(int argc,char *argv[ ])
——————-
int sin(double x);
int (*pf)( );
pf=sin;
pf是一个指向返回值为int型值的函数的指针
如果不加括号,可以理解为int* pf( );
——————-
汉诺塔hanoi ( Wed, 8 Apr 2009 14:49:17 +0800 )
Description: —————————————————
#include <iostream.h>
int count;
int move(int nth,char from,char to){
cout<<++count<<‘ ‘<<“move:”<<nth<<” “<<from<<“->”<<to<<endl;
}
int hanoi(int n,char from,char mid,char to){
if(1==n) move(n,from,to);
else{
hanoi(n-1,from,to,mid);
move(n,from,to);
hanoi(n-1,mid,from,to);
}
}
int main(){
count=0;
cout<<endl;
hanoi(3,’A’,’B’,’C’);
count=0;
cout<<endl;
hanoi(4,’A’,’B’,’C’);
}
—————————————————
[zhangzl@nsw-s-crosscompile ~]$ g++ -o hanoi hanoi.cpp
[zhangzl@nsw-s-crosscompile ~]$ ./hanoi
1 move:1 A->C
2 move:2 A->B
3 move:1 C->B
4 move:3 A->C
5 move:1 B->A
6 move:2 B->C
7 move:1 A->C
1 move:1 A->B
2 move:2 A->C
3 move:1 B->C
4 move:3 A->B
5 move:1 C->A
6 move:2 C->B
7 move:1 A->B
8 move:4 A->C
9 move:1 B->C
10 move:2 B->A
11 move:1 C->A
12 move:3 B->C
13 move:1 A->B
14 move:2 A->C
15 move:1 B->C
查找:二叉排序树 ( Mon, 22 Dec 2008 13:21:51 +0800 )
Description: //Mon Dec 22 10:21:54 CST 2008
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
//查找:二叉排序树
typedef struct bsnode{
int key;
struct bsnode *lchild,*rchild;
}bsnode;
//插入
bsnode *InsertBsTree(bsnode **root,int target){
bsnode *p,*q;
q=*root;
//寻找插入位置,q遍历,p记录q的父节点,以便插入时使用
while(q){
//target已经存在,不插入
if(q->key==target){
return NULL;
}else if(q->key>target){
p=q;
q=q->lchild;
}else{
p=q;
q=q->rchild;
}
}
//分配新节点
q=(bsnode *)malloc(sizeof(bsnode));
memset(q,0x0,sizeof(bsnode));
q->key=target;
//插入
if((*root)==NULL){//如果没有根节点,q为根
*root=q;
}else if(q->key<p->key){
p->lchild=q;
}else{
p->rchild=q;
}
return q;
}
//二叉排序树的中序遍历是递增序列
void MidTraverse(bsnode *pTree){
if(pTree==NULL) return;
else{
MidTraverse(pTree->lchild);
printf(“%d\n”,pTree->key);
MidTraverse(pTree->rchild);
}
}
//查找,递归算法
bsnode *Bssearch(bsnode *root,int target){
if(root==NULL){
return NULL;
}else if(target==root->key){
return root;
}else if(target<root->key){
//注意:这里的return不可少.
//因为这里要返回调用函数的return值,否则系统会给一个随机值.
//类似于int foo(){}
//foo()虽然定义了返回类型int,但没有给出return值,foo的return值随机.
return Bssearch(root->lchild,target);
}else{
return Bssearch(root->rchild,target);
}
}
//删除
//(1)待删除的节点是叶子节点
//(2)待删除的节点只有右子树而无左子树,或只有左子树而无右子树
//(3)待删除的节点同时有左右子树
int main(){
int a[]={10,8,6,9,7,11,13,16,20,12,18,15,4,16};
bsnode *root=NULL;
bsnode *find=NULL;
int i=0;
for(i=0;i<sizeof(a)/sizeof(a[0]);i++)
//a[i]的顺序会决定二叉排序树的结构
//如果a[i]递增,会出现二叉排序树为歪斜树(只有左孩子或右孩子)
//所以需要平衡处理,即平衡二叉树(AVL树,Adelson-Velskii and Landis)
//平衡二叉树的构造:旋转
InsertBsTree(&root,a[i]);
MidTraverse(root);
find=Bssearch(root,15);
if(find!=NULL){
printf(“find %d\n”,find->key);
}
return 0;
}
内部排序算法 ( Thu, 18 Dec 2008 17:24:07 +0800 )
Description:
//Tue Dec 16 18:15:55 CST 2008
//内部排序算法
//1插入
//直接插入排序
//折半插入排序
//希尔排序
//2交换
//冒泡排序
//快速排序
//3选择
//简单选择排序
//堆排序
//4
//归并排序(归并排序也是外排序的主要思想)
//基数排序
#include <stdio.h>
#include <stdlib.h>
//宏定义,求数组元素个数
#define N(x) sizeof((x))/sizeof((x)[0])
//因为在工程中其他文件定义了Print,所以这里声明为static
static void Print(int a[], int n){
int i;
for(i=0;i<n;i++)
printf(“%d “,a[i]);
printf(“\n”);
}
//———————插入排序——————-//
//直接插入排序O(n*n)
/*思想:先将有序序列中的第1个元素看作是有序序列的子序列,然后从第2个记录开始逐个进行插入*/
/*直至整个序列变成按关键字非递减的有序序列为止。*/
void InsertSort(int a[], int n){
int tmp;
int i,j;
for(i=1;i<n;i++){
tmp=a[i];
for(j=i-1;j>=0&&tmp<a[j];j–){//如果a[j]>data,a[j]就向后移动,空出插入位置
a[j+1]=a[j];
}
a[j+1]=tmp;
}
}
//折半插入排序O(n*n)
/*思想:与折半查找类似*/
void InsertHalfSort(int a[], int n){
int i,j;
int high,low,mid;
int tmp;
for(i=0;i<n;i++){
tmp=a[i];
low=0;
high=i-1;
while(low<=high){//折半查找tmp的插入位置
mid=(low+high)/2;
if(tmp<=a[mid]) high=mid-1;
else low=mid+1;
}
for(j=i-1;j>=high+1;j–){//找到位置后,将其后元素后移,空出插入位置
a[j+1]=a[j];
}
a[j+1]=tmp;//插入
}
}
//希尔排序,缩小增量排序O(n1.5)
/*思路:先将整个待排序列分为几个子序列,分别进行直接插入排序,待整个序列中的记录基本有序时*/
/*再对全体记录进行一次直接插入排序*/
void ShellSort(int a[],int n)
{
int i,j,tmp;
int d=n/2;//缩小增量
while(d>0){
for(i=d;i<n;i++){//如果d=1,则退化为直接插入排序
tmp=a[i];
for(j=i-d;j>=0&&tmp<a[j];j-=d){
a[j+d]=a[j];
}
a[j+d]=tmp;
}
d=d/2;
}
}
//———————交换排序——————-//
void BubbleSort(int a[],int n)
{
int i,j,tmp;
char flag;
for(i=0;i<n;i++){
flag=0;//每次比较前设置flag=0
//排好的序列升到数组的最前端,
//or排好的序列降到最末端:for(j=1;j<n-i;j++)
for(j=n-1;j>i;j–){
if(a[j-1]>a[j]){
flag=1;//如果有逆序,则置flag=1
tmp=a[j-1];
a[j-1]=a[j];
a[j]=tmp;
}
}
if(flag==0){//没有逆序发生,则排序完成
return;
}
}
}
/*快速排序,分区交换排序,对冒泡排序的一种改进,n较大时,快速排序是最好的内部排序方法*/
/*O(n*log(n)/log(2))*/
/*求lnx为log(x),求log 10 x是log10(x)*/
/*没有专门的求任意底数对数的函数,不过可以用log(x)/log(y)表示log y x*/
/*思想:通过一趟排序,将待排记录分为独立的两部分,其中一部分记录的关键字均比另一部分的关键字小*/
/* 则可对这两部分记录继续进行排序,以达到整个序列有序*/
/*一趟快速排序的做法*/
/*附设两个指针left,righ,分别指向第1个记录和第n个记录,设关键字为tmp,指向第一个记录*/
/*1.首先从righ位置向前搜索,直到搜到比tmp小的记录,与tmp进行交换*/
/*2.从left位置向后搜索,知道搜到比tmp大的记录,与tmp进行交换*/
/*3.重复这两步,直到left = righ为止*/
/*理想的情况下,关键字tmp应该是当前序列的中值*/
void QuickSort(int a[],int left,int right)
{
int i,j,tmp;
if(left<right){//递归结束的条件
i=left,j=right,tmp=a[left];
while(i<j){
while(i<j&&tmp<=a[j]){
j–;
}
if(i<j) a[i]=a[j];
while(i<j&&tmp>=a[i]){
i++;
}
if(i<j) a[j]=a[i];
}
a[i]=tmp;//a[i]是tmp的最终位置
QuickSort(a,left,i-1);//a[i]的左边排序
QuickSort(a,i+1,right);//a[i]的右边排序
}
}
//———————选择排序——————-//
void SimpleSelectSort(int a[],int n)
{
int i,j,min,tmp;
for(i=0;i<n;i++){
for(j=i+1,min=i;j<n;j++){
if(a[j]<a[min]){
min=j;
}
}
if(min!=i){
tmp=a[min];
a[min]=a[i];
a[i]=tmp;
}
}
}
int main(){
int num[]={58,46,72,95,84,25,37,58,63,12};
InsertSort(num,N(num));
// InsertHalfSort(num,N(num));
// ShellSort(num,N(num));
// BubbleSort(num,N(num));
// QuickSort(num,0,N(num)-1);
// SimpleSelectSort(num,N(num));
Print(num,N(num));
return 0;
}
tar解压部分文件 ( Thu, 18 Dec 2008 13:06:36 +0800 )
Description: tar部分解压,只解压出需要的文件,这样就解决了tar包过大的情况下,解压速度太慢、解压后占用空间过大的问题。
几个步骤:
查看tar包内包含的文件,如果已经知道这一步可免
tar -tzvf u2file.tar.gz
-rw-r–r– user/user 45489156 2008-08-04 23:59:46 foder/access.log.20080804
-rw-r–r– user/user 37469223 2008-08-05 23:59:46 foder/access.log.20080805
#解压单个文件
tar -zxvf u2file.tar.gz foder/access.log.0805
#解压多个文件
tar -zxvf u2file.tar.gz foder/access.log.*
#解压到指定目录
tar -xzvf u2file.tar.gz foder/access.log.0805 -C /new/dir/ # -C 指定解压到的目录.
八数码(广度优先)源代码 ( Tue, 16 Dec 2008 13:11:46 +0800 )
Description: #include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct node
{
int row;
int col;
int eight[3][3];
int move[9][4];
struct node *next;
struct node *parent;
}Tnode;
Tnode *head,*tail;
int g_endstat[3][3]={{1,2,3},{8,0,4},{7,6,5}};
void PrintNode(Tnode *tmp);
void InsertNode(Tnode *tmp,Tnode *parent);
int IsExist(int a[][3]);
int Search(Tnode *current);
void MoveZero(Tnode *current,int mark);
void InsertNode(Tnode *tmp,Tnode *parent)
{
if(head == NULL){
head=(Tnode *)tmp;
tail=head;
tail->next=NULL;
head->parent=NULL;
}else{
if(IsExist(tmp->eight)==1){
return;
}
tail->next=(Tnode *)tmp;
tail=(Tnode *)tmp;
tail->next=NULL;
if(parent!=NULL){
tmp->parent=parent;
}else{
tmp->parent=NULL;
}
}
return;
}
int IsExist(int a[][3])
{
Tnode *tmp;
int i,j;
for(tmp=head;tmp->next!=NULL;tmp++){
for(i=0;i<3;i++){
for(j=0;j<3;j++){
if(tmp->eight[i][j]!=a[i][j])
break;
}
if(j<3) break;
}
if(i>=3&&j>=3)
return 1;
}
return 0;
}
void PrintNode(Tnode *tmp)
{
int i,j;
printf(“\n\nrow=%d,col=%d\n”,tmp->row,tmp->col);
for(i=0;i<3;i++){
for(j=0;j<3;j++){
printf(“%d “,tmp->eight[i][j]);
}
printf(“\n”);
}
}
int IsMatch(int current[][3],int end[][3])
{
int i,j;
for(i=0;i<3;i++){
for(j=0;j<3;j++){
if(current[i][j]!=end[i][j])
return 0;
}
}
return 1;
}
int Search(Tnode *current){
Tnode *child=NULL;
int i;
for(i= 0; i<4; i++)
{
if(current->move[3*current->row+current->col][i] != 0){
child=(Tnode *)malloc(sizeof(Tnode));
memcpy(child,current,sizeof(Tnode));
MoveZero(child,i);
PrintNode(child);
if(IsMatch(child->eight,g_endstat)==0){
InsertNode(child,current);
}else{
printf(“Match!\n”);
return 1;
}
}
}
return 0;
}
void MoveZero(Tnode *current,int mark)
{
int exchange=-1;
int row=current->row;
int col=current->col;
if((mark == 0) && col >=1)
{//向左
exchange = current->eight[row][col-1];
current->eight[row][col-1] = current->eight[row][col];
current->eight[row][col] = exchange;
current->move[3*row+col-1] = 0;
current->col–;
return;
}
if((mark == 1) && col < 2)
{//向右
exchange = current->eight[row][col+1];
current->eight[row][col+1] = current->eight[row][col];
current->eight[row][col] = exchange;
current->move[3*row+col+1] = 0;
current->col++;
return;
}
if((mark == 2) && row >= 1)
{//向上
exchange = current->eight[row-1][col];
current->eight[row-1][col] = current->eight[row][col];
current->eight[row][col] = exchange;
current->move[3*(row-1)+col] = 0;
current->row–;
return;
}
if((mark == 3) && row < 2)
{ //向下
exchange = current->eight[row+1][col];
current->eight[row+1][col] = current->eight[row][col];
current->eight[row][col] = exchange;
current->move[3*(row+1)+col] = 0;
current->row++;
return;
}
return;
}
//更新结构体中的move;根据move,可判断定空格是否可移动
void CopyMoveDirection(int move[][4],int copyMove[][4])
{
int i,j;
for(i= 0; i<9; i++)
for(j= 0; j<4; j++)
move[i][j] = copyMove[i][j];
}
//复制函数
void Copy(int target[][3],int resource[][3])
{
int i,j;
for(i=0; i<3; i++)
for(j=0; j<3; j++)
target[i][j] = resource[i][j];
}
//定位空格的位置,其中零表示空格
void Find(int a[][3],int *row,int *col)
{
int i,j;
for(i= 0; i<3; i++)
{
for(j=0; j<3; j++)
if(a[i][j] == 0){
*row=i;
*col=j;
break;
}
}
}
void PrintPath(Tnode *current){
printf(“path:\n”);
for(;current!=NULL;current=current->parent)
PrintNode(current);
}
void Free(){
Tnode *tmp=head;
while(head){
head=head->next;
free(tmp);
tmp=head;
}
}
int mainbfs()
{
Tnode start;
int i=0;
// int eight[3][3]={{1,2,3},{8,6,4},{7,0,5}};
int eight[3][3]={2,8,3,1,0,4,7,6,5};
int move[9][4]={{0,-1,0,-2},{1,-1,0,-2},{1,0,0,-2},
{0,-1,2,-2},{1,-1,2,-2},{1,0,2,-2},
{0,-1,2,0}, {1,-1,2,0}, {1,0,2,0}};
Find(eight,&start.row,&start.col);
Copy(&start.eight,eight);
CopyMoveDirection(&start.move,move);
start.next=NULL;
start.parent=NULL;
Tnode *current;
InsertNode(&start,NULL);
current=head;
do{
printf(“****node%d****\n”,i++);
if(Search(current)==1) break;
current=current->next;
}while(current!=NULL);
PrintPath(current);
Free();
return 0;
}
链表 ( Tue, 16 Dec 2008 13:04:29 +0800 )
Description: #include <stdio.h>
#include <stdlib.h>
typedef struct LNode{
int data;
struct LNode *next;
}LNode,*LinkList;
LinkList head,tail;
//建立头结点,形式参数为二级指针
void InitList(LinkList *p){
(*p)=(LinkList)malloc(sizeof(LNode));
(*p)->data=-1;
(*p)->next=NULL;
}
//按照由小到大的顺序插入排序
void Insert(LinkList h,int a){
LinkList p,q;
q=h;
while(q){
if(a>q->data){
p=q;
q=q->next;
}else{
printf(“break\n”);
break;
}
}
p->next=(LinkList)malloc(sizeof(LNode));
p->next->data=a;
p->next->next=q;
}
//将链表反序
void Reverse(LinkList h){
LinkList p,q,tmp;
p=NULL;
q=head->next;
while(q){
tmp=q->next;
q->next=p;
p=q;
q=tmp;
}
h->next=p;
}
//删除某个节点
int Delete(LinkList p,int key){
LinkList q;
q=p;
p=p->next;
while(p){
if(p->data==key)
break;
q=p;
p=p->next;
}
if(p){//找到节点
q->next=p->next;
return 1;
}
//没找到节点
return 0;
}
void Print(LinkList p){
p=p->next;
while(p){
printf(“%d\n”,p->data);
p=p->next;
}
}
int main(){
int i=0;
int data=0;
InitList(&head);
printf(“input five numbers:\n”);
for(i=0;i<5;i++){
scanf(“%d”,&data);
Insert(head,data);
}
printf(“original:\n”);
Print(head);
printf(“after delete 2:\n”);
Delete(head,2);
Print(head);
printf(“after delete 8,ret=%d:\n”,Delete(head,8));
Print(head);
printf(“reverse:\n”);
Reverse(head);
Print(head);
return 0;
}
二叉树的建立和遍历、引用和二级指针传参 ( Mon, 15 Dec 2008 14:17:15 +0800 )
Description: 二叉树
a
/ \
b d
/
c
#include <stdio.h>
#include <stdlib.h>
typedef struct BiTNode{
char ch;
struct BiTNode *lchild;
struct BiTNode *rchild;
}BiTNode,*BiTree;
//二级指针
void CreatTree(BiTree *pTree)
{
char ch;
if((ch=getchar())==’/’) *pTree=NULL;
else{
*pTree=(BiTree)malloc(sizeof(BiTNode));
(*pTree)->ch=ch;
CreatTree(&(*pTree)->lchild);
CreatTree(&(*pTree)->rchild);
}
}
//返回值
BiTree CreatTree2()
{
BiTree pTree;
char ch;
if((ch=getchar())==’/’) pTree=NULL;
else{
pTree=(BiTree)malloc(sizeof(BiTNode));
pTree->ch=ch;
pTree->lchild=CreatTree2();
pTree->rchild=CreatTree2();
}
return pTree;
}
//引用 C++
void CreatTree3(BiTree &pTree)
{
char ch;
if((ch=getchar())==’/’) pTree=NULL;
else{
pTree=(BiTree)malloc(sizeof(BiTNode));
pTree->ch=ch;
CreatTree3(pTree->lchild);
CreatTree3(pTree->rchild);
}
}
void Traverse(BiTree pTree)
{
if(pTree==NULL) return;
else{
printf(“%c\n”,pTree->ch);
Traverse(pTree->lchild);
Traverse(pTree->rchild);
}
}
//输入:abc///d// (前序)
int main()
{
BiTNode *root=NULL;
//CreatTree(&root);
//root=CreatTree2();
CreatTree3(root);
Traverse(root);
return 0;
}
==================================
建立二叉树时,要递归的修改指针的值,可采用的方法有:二级指针、引用(C++)、返回值。
具体使用方法如上示。注意函数的声明方法和调用方法。
★引用和取地址的区别:函数形参表里的&是引用,函数实参表里的&是取地址 。
下面是错误的使用方法:
void CreatTree(BiTree pTree)
{
char ch;
if((ch=getchar())==’/’) pTree=NULL;
else{
pTree=(BiTree)malloc(sizeof(BiTNode));
CreatTree(pTree->lchild);
CreatTree(pTree->rchild);
}
}
BiTNode *root=NULL;
CreatTree(root); //root的值不会更改。
//或 BiTNode root;
// CreatTree(&root); //pTree=(BiTree)malloc(sizeof(BiTNode))冲突;
pmap ( Wed, 10 Dec 2008 18:10:54 +0800 )
Description:
int main(){
int m=0;
int j[9*8*7*6*5*4*3*2*1]={0};
m=sizeof(j);
}
[root@localhost ~]# pmap -x pid
6691: /root/a.out
Address Kbytes RSS Anon Locked Mode Mapping
00194000 4 – – – r-x– [ anon ]
08048000 4 – – – r-x– a.out
08049000 4 – – – rwx– a.out
461f9000 100 – – – r-x– ld-2.5.so
46212000 4 – – – r-x– ld-2.5.so
46213000 4 – – – rwx– ld-2.5.so
46216000 1244 – – – r-x– libc-2.5.so
4634d000 8 – – – r-x– libc-2.5.so
4634f000 4 – – – rwx– libc-2.5.so
46350000 12 – – – rwx– [ anon ]
b7fc8000 4 – – – rw— [ anon ]
b7fdb000 4 – – – rw— [ anon ]
bfc3d000 1432 – – – rw— [ stack ]
——– ——- ——- ——- ——-
total kB 2828 – – –
[root@localhost ~]# man pmap
NAME
pmap – report memory map of a process
A*算法 ( Wed, 10 Dec 2008 16:55:12 +0800 )
Description: A*(A-Star)算法是一种静态路网中求解最短路最有效的方法。
公式表示为: f(n)=g(n)+h(n), 其中f(n) 是节点n从初始点到目标点的估价函数g(n)是在状态空间中从初始节点到n节点的实际代价h(n)是从n到目标节点最佳路径的估计代价。保证找到最短路径(最优解的)条件,关键在于估价函数h(n)的选取:
估价值h(n)<= n到目标节点的距离实际值,这种情况下,搜索的点数多,搜索范围大,效率低。但能得到最优解。如果 估价值>实际值, 搜索的点数少,搜索范围小,效率高,但不能保证得到最优解。估价值与实际值越接近,估价函数取得就越好。
例如对于几何路网来说,可以取两节点间欧几理德距离(直线距离)做为估价值,即f=g(n)+sqrt((dx-nx)*(dx-nx)+(dy-ny)*(dy-ny));这样估价函数f在g值一定的情况下,会或多或少的受估价值h的制约,节点距目标点近,h值小,f值相对就小,能保证最短路的搜索向终点的方向进行。明显优于Dijstra算法的毫无无方向的向四周搜索。
主要搜索过程:
创建两个表,OPEN表保存所有已生成而未考察的节点,CLOSED表中记录已访问过的节点。
遍历当前节点的各个节点,将n节点放入CLOSE中,取n节点的子节点X,->算X的估价值->
While(OPEN!=NULL)
{
从OPEN表中取估价值f最小的节点n;
if(n节点==目标节点) break;
else
{
if(X in OPEN) 比较两个X的估价值f //注意是同一个节点的两个不同路径的估价值
if( X的估价值小于OPEN表的估价值 )
更新OPEN表中的估价值; //取最小路径的估价值
if(X in CLOSE) 比较两个X的估价值 //注意是同一个节点的两个不同路径的估价值
if( X的估价值小于CLOSE表的估价值 )
更新CLOSE表中的估价值; 把X节点放入OPEN //取最小路径的估价值
if(X not in both)
求X的估价值;
并将X插入OPEN表中; //还没有排序
}
将n节点插入CLOSE表中;
按照估价值将OPEN表中的节点排序; //实际上是比较OPEN表内节点f的大小,从最小路径的节点向下进行。
}
启发式搜索其实有很多的算法,比如:局部择优搜索法、最好优先搜索法等等。当然A*也是。这些算法都使用了启发函数,但在具体的选取最佳搜索节点时的策略不同。
局部择优搜索法,就是在搜索的过程中选取“最佳节点”后舍弃其他的兄弟节点,父亲节点,而一直得搜索下去。这种搜索的结果很明显,由于舍弃了其他的节点,可能也把最好的节点都舍弃了,因为求解的最佳节点只是在该阶段的最佳并不一定是全局的最佳。
最好优先就聪明多了,他在搜索时,便没有舍弃节点(除非该节点是死节点),在每一步的估价中都把当前的节点和以前的节点的估价值比较得到一个“最佳的节点”。这样可以有效的防止“最佳节点”的丢失。
那么A*算法又是一种什么样的算法呢?其实A*算法也是一种最好优先的算法。只不过要加上一些约束条件罢了。由于在一些问题求解时,我们希望能够求解出状态空间搜索的最短路径,也就是用最快的方法求解问题,A*就是干这种事情的!
我们先下个定义,如果一个估价函数可以找出最短的路径,我们称之为可采纳性。A*算法是一个可采纳的最好优先算法。A*算法的估价函数可表示为:
f'(n) = g'(n) + h'(n)
这里,
f'(n)是估价函数,g'(n)是起点到n的最短路径值,h'(n)是n到目标的最短路经的值。由于这个f'(n)其实是无法预先知道的,所以我们用前面的估价函数f(n)做近似。g(n)代替g'(n),但g(n)>=g'(n)才可(大多数情况下都是满足的,可以不用考虑),h(n)代替h'(n),但h(n)<=h'(n)才可(这一点特别的重要)。可以证明应用这样的估价函数是可以找到最短路径的,也就是可采纳的。我们说应用这种估价函数的最好优先算法就是A*算法。
举一个例子,其实广度优先算法就是A*算法的特例。其中g(n)是节点所在的层数,h(n)=0,这种h(n)肯定小于h'(n),所以由前述可知广度优先算法是一种可采纳的。实际也是。当然它是一种最臭的A*算法。
再说一个问题,就是有关h(n)启发函数的信息性。h(n)的信息性通俗点说其实就是在估计一个节点的值时的约束条件,如果信息越多或约束条件越多则排除的节点就越多,估价函数越好或说这个算法越好。这就是为什么广度优先算法的那么臭的原因了,谁叫它的h(n)=0,一点启发信息都没有。但在游戏开发中由于实时性的问题,h(n)的信息越多,它的计算量就越大,耗费的时间就越多。就应该适当的减小h(n)的信息,即减小约束条件。但算法的准确性就差了,这里就有一个平衡的问题。
八数码问题分析【转】 ( Tue, 9 Dec 2008 15:40:35 +0800 )
Description: 问题简介:
所谓八数码问题是指这样一种游戏:将分别标有数字1,2,3,…,8的八块正方形数码牌任意地放在一块3×3的数码盘上。放牌时要求不能重叠。于是,在3×3的数码盘上出现了一个空格。现在要求按照每次只能将与空格相邻的数码牌与空格交换的原则,将任意摆放的数码盘逐步摆成某种特殊的排列。如下图表示了一个具体的八数码问题求解。
问题分析:
首先,八数码问题包括一个初始状态(START)和目标状态(END),所谓解八数码问题就是在两个状态间寻找一系列可过渡状态(START->STATE1->STATE2->…->END)。这个状态是否存在就是我们要解决的第一个问题:
Q1:每一个状态及每一次操作的表示方法?
有许多表示方法,比如一个3*3的八数码盘可以压缩成一个int值表示,但不适用于15 puzzle或大于8 的puzzle问题。如果对空间要求很高,应该还可以再压缩。本文采用一个int表示的方法。
表示方法如下:由于int的表示范围大于1e9,所以我们取一个int的低9位,为了方便寻找空格的位置,int的个位我们用来放空格的位置(1-9)。而前8位,按照行从上到下,列从左到右的顺序依次记录对应位置上的数字。例如:
可以表示成 2 3 1 5 8 4 6 7 5 ,个位的5表示空格在第5位,前八位数按顺序记录。坐标转换公式为:
num(压缩后的int) x y(求x行y列,1记起)1e(n)为 1乘10的n次
int temp=(x-1)*3+y
if temp > num%10 then return (num / 1e(9-temp+1)) %10
else return (num / 1e(9-temp) )%10
为了方便本文介绍,取目标状态为:1 2 3 4 5 6 7 8 9 即–>
操作本文用 u r d l 分别表示 空格的向上 向右 向下 向左 四个操作。比如,在简介中的图包括两步操作 l d ,可能与平时玩这类游戏的习惯不符合,但这是为了和ACM例题相统一。
对应地,每种操作引起的状态变化如下:
r :num值++ l :num值–
u :有点复杂
int t0 = 9-num%10 + 1
int t1 = num / 1e(t0)
int t2 = t1%1000
t1= t1- t2 + (t2 % 100) * 10 + t2 / 100
t1*= 1e(t0)
return (t1 + ( (num % t0) – 3))
d :return前面同u操作, return返回 (t1 + ( (num % t0) + 3))
Q2:判断是否存在中间状态使START 到达END?
用组合数学的方法可以快速地进行判断,例如SOJ 2061题 2360题中都是关于此类的问题。但八数码的判断方法比他们简单多了。
本文用的方法是计算排列的逆序数值,以2 3 1 5 8 4 6 7 5 为例子,5表示的是空格,不计算,那么求23158467 的逆序值为
0 + 0 + 2 (1<2 1<3 ) + 0 + 0 + 1 ( 4<5 ) + 1 ( 6<8 ) + 1 ( 7<8 ) = 5
目标状态1 2 3 4 5 6 7 8 9 的逆序自然就是0。
两个状态之间是否可达,可以通过计算两者的逆序值,若两者奇偶性相同则可达,不然两个状态不可达。
简单证明一下:
l 和 r 操作,不会影响状态的逆序值,因为只会改变个位数(空格的位置)。
u和d操作是使某个位置的数字 右/左 移两位。 由于数字序列的每一次移动会使逆序值奇偶性改变,所以 移动两次后奇偶性不变。
所以 四个操作均不会影响序列的奇偶性。
Q3:如何寻找一系列的中间状态及遇到的问题?
要寻找这一系列中间状态的方法是搜索,但搜索很容易遇到时间和空间上的问题。以下就是搜索的基本原理:
由1 3 7 2 4 6 8 5 2 状态可以衍生三个状态,假如选择了1 2 3 7 4 6 8 5 5 ,则又衍生三个状态,继续按某策略进行选择,一直到衍生出的新状态为目标状态END 为止。
容易看出,这样的搜索类似于从树根开始向茎再向叶搜索目标叶子一样的树型状。由于其规模的不断扩大,其叶子也愈加茂密,最终的规模会大到无法控制。这样在空间上会大大加大搜索难度,在时间上也要消耗许多。
在普通搜索中遇到以下问题:
a 搜索中易出现循环,即访问某一个状态后又来访问该状态。
b 搜索路径不佳便无法得到较好的中间状态集(即中间状态集的元素数量过大)。
c 搜索过程中访问了过多的无用状态,这些状态对最后的结果无帮助。
以上三个问题中,a为致命问题,应该它可能导致程序死循环;b和c是非致命的,但若不处理好可能导致性能急剧下降。
Q4:怎样避免重复访问一个状态?
最直接的方法是记录每一个状态访问否,然后再衍生状态时不衍生那些已经访问的状态了。思想是,给每个状态标记一个flag,若该状态flag = true则不衍生,若为false则衍生并修改flag为true。
在某些算法描述里,称有两个链表,一个为活链表(待访问),一个为死链表(访问完)。每一次衍生状态时,先判断它是否已在两个链表中,若存在,则不衍生;若不存在,将其放入活链表。对于被衍生的那个状态,放入死链表。
为了记录每一个状态是否被访问过,我们需要有足够的空间。八数码问题一共有9!,这个数字并不是很大,但迎面而来的另一个问题是我们如何快速访问这些状态,如果是单纯用链表的话,那么在规模相当大,查找的状态数目十分多的时候就不能快速找到状态,其复杂度为O(n),为了解决这个问题,本文将采用哈希函数的方法,使复杂度减为O(1)。
这里的哈希函数是用能对许多全排列问题适用的方法。取n!为基数,状态第n位的逆序值为哈希值第n位数。对于空格,取其(9-位置)再乘以8!。例如,1 3 7 2 4 6 8 5 8 的哈希值等于:
0*0! + 0*1! + 0*2! + 2*3! + 1*4! + 1*5! + 0*6! + 3*7! + (9-8)*8! = 55596 <9!
具体的原因可以去查查一些数学书,其中1 2 3 4 5 6 7 8 9 的哈希值是0 最小,8 7 6 5 4 3 2 1 0 的哈希值是(9!-1)最大,而其他值都在0 到 (9!-1) 中,且均唯一。
Q5:如何使搜索只求得最佳的解?
普通的搜索称为DFS(深度优先搜索)。除了DFS,还有BFS,从概念上讲,两者只是在扩展时的方向不同,DFS向深扩张,而BFS向广扩张。在八数码问题的解集树中,树的深度就表示了从初始态到目标态的步数,DFS一味向深,所以很容易找出深度较大的解。
BFS可以保证解的深度最少,因为在未将同一深度的状态全部访问完前,BFS不会去访问更深的状态,因此比较适合八数码问题,至少能解决求最佳解的难题。
但是BFS和DFS一样不能解决问题c ,因为每个状态都需要扩张,所以广搜很容易使待搜状态的数目膨胀。最终影响效率。
Q6:该如何减少因广搜所扩张的与目标状态及解无关的状态?
前面所说的都是从START状态向END状态搜索,那么,将END状态与START状态倒一下,其实会有另一条搜索路径(Q8策略三讨论),但简单的交换END与START并不能缩小状态膨胀的规模。我们可以将正向与反向的搜索结合起来,这就是双向广度搜索。
双向广搜是指同时从START和END两端搜,当某一端所要访问的一个状态是被另一端访问过的时候,即找到解,搜索结束。它的好处是可以避免广搜后期状态的膨胀。
采用双向广度搜索可以将空间和时间节省一半!
Q7:决定一个快的检索策略?
双向广搜能大大减少时间和空间,但在有的情况下我们并不需要空间的节省,比如在Q4中已经决定了我们需要使用的空间是9!,所以不需要节省。这样我们可以把重点放在时间的缩短上。
启发式搜索是在路径搜索问题中很实用的搜索方式,通过设计一个好的启发式函数来计算状态的优先级,优先考虑优先级高的状态,可以提早搜索到达目标态的时间。A*是一种启发式搜索的,他的启发式函数f ‘ ()=g’ () + h’ () 能够应用到八数码问题中来。
g’ () —– 从起始状态到当前状态的实际代价g*()的估计值,g’ () >= g*()
h’ () —– 从当前状态到目标状态的实际代价h*()的估计值,h’ () <= h*()
注意两个限制条件:
(1)h’ () <= h*() (2)任意状态的f ‘()值必须大于其父状态的f ‘()值,即f ‘()单调递增。
其中,g’ () 是搜索的深度, h’ ()则是一个估计函数,用以估计当前态到目标态可能的步数。解八数码问题时一般有两种估计函数。比较简单的是difference ( Status a,Status b ), 其返回值是a 和b状态各位置上数字不同的次数。另一种比较经典的是曼哈顿距离 manhattan ( Statusa, Status b ),其返回的是各个数字从a的位置到b的位置的距离(见例子)。
例如状态 1 3 7 2 4 6 8 5 2 和状态 1 2 3 4 5 6 7 8 9 的difference 是5(不含空格)。而他的manhattan 距离是:
1 (7d一次) + 1 (2u一次) + 2 (4l两次) + 3 (6r两次u一次) + 2 (5u一次l一次) = 9
单个数字的manhattan应该小于5,因为对角的距离才4,若大于4则说明计算有误。
无论是difference还是manhattan,估计为越小越接近END,所以优先级高。
在计算difference和manhattan时,推荐都将空格忽略,因为在difference中空格可有可无,对整体搜索影响不大。
本文后面的实现将使用manhattan不计空格的方法。其实,每移动一步,不计空格,相当于移动一个数字。如果每次移动都是完美的,即把一个数字归位,那么START态到END态的距离就是manhattan。反过来说,manhattan是START到END态的至少走的步数。
回到f ‘()=g’ ()+ h’ (),其实广度搜索是h’ ()=0的一种启发式搜索的特例,而深度搜索是 f ‘ ()=0 的一般搜索。h’ ()对于优化搜索速度有很重要的作用。
Q8:能否进一步优化检索策略?
答案是肯定的。
A*搜索策略的优劣就是看h’ ()的决定好坏。前面列举了两个h’ ()的函数,但光有这两个是不够的。经过实验分析,在f ‘()中,g ‘()决定的是START态到END态中求得的解距离最优解的距离。 而h’ () 能影响搜索的速度。
所以优化的第一条策略是,放大h’ (),比如,让h ‘()= 10* manhattan(),那么f ‘()= g’ ()+10*manhattan(),可能提高搜索速度。可惜的是所得的解将不再会是最优的了。
为什么放大h'()能加快搜索速度,我们可以想象一下,h'()描述的是本状态到END态的估计距离,估计距离越短自然快一点到达END态。而 g'()描述的是目前的深度,放大h'()的目的是尽量忽略深度的因素,是一种带策略的深搜,自然速度会比广搜和深搜都快,而因为减少考虑了深度因素,所以离最优解就越来越远了。关于h'()放大多少,是很有趣的问题,有兴趣可以做些实验试试。
第二条是更新待检查的状态,由于A*搜索会需要一个待检查的序列。首先,在Q4已经提到用哈希避免重复访问同一状态。而在待检查队列中的状态是未完成扩张的状态,如果出现了状态相同但其g ‘()比原g'()出色的情况,那么我们更希望的是搜索新状态,而不是原状态。这样,在待检查队列中出现重复状态时,只需更新其g'() 就可以了。
第三条是注意实现程序的方法,在起初我用sort排序f'()后再找出权值最大的状态,而后发现用make_heap要更快。想一想,由于需要访问的接点较多,待访问队列一大那么自然反复排序对速度会有影响,而堆操作则比排序更好。另一点是,实现更新待检查队列时的搜索也要用比较好的方法实现。我在JAVA的演示程序中用的PriorityQueue,可是结果不是很令人满意。
第四条优化策略是使用IDA*的算法,这是A*算法的一种,ID名为Iterative deepening是迭代加深的意思。思想是如下:
顺便准备一个记录一次循环最小值的temp=MAX, h’ 取 manhattan距离
先估算从START态到END态的h'() 记录为MIN,将START放入待访问队列
读取队列下一个状态,到队列尾则GOTO⑦
若g'() > MIN GOTO ⑥
若g'() + h'() > MIN 是否为真,真GOTO ⑥,否 GOTO ⑤
扩展该状态,并标记此状态已访问。找到END态的话就结束该算法。GOTO ②
temp = min(manhattan , temp),GOTO ③
若无扩展过状态,MIN=temp (ID的意思在这里体现)从头开始循环GOTO ②
第五条优化策略本身与搜索无关,在做题时也没能帮上忙,不过从理论上讲是有参考价值的。记得Q6中介绍的从END开始搜起吗?如果我们的任务是对多个START与END进行搜索,那么我们可以在每搜索完一次后记录下路径,这个路径很重要,因为在以后的搜索中如果存在START和END的路径已经被记录过了,那么可以直接调出结果。
从END搜起,可以方便判断下一次的START是否已经有路径到END了。当前一次搜索完时,其已访问状态是可以直接使用的,若START不在其中,则从待访问的状态链表中按搜索策略找下一个状态,等于接着上一次的搜索结果开始找。
之所以没能在速度上帮上忙,是因为这个优化策略需要加大g’ ()的比重,否则很容易出现深度相当大的情况,由于前一次搜索的策略与下一次的基本无关,导致前一次的路径无法预料,所以就会出现深度过大的情况。解决方法是加大g’ ()。
策略五类似给程序加一个缓冲区,避免重复计算。如果要做八数码的应用,缓冲区会帮上忙的。
Q10:怎样记录找到的路径?
当找到解的时候我们就需要有类似回退的工作来整理一条解路径,由于使用了不是简单的DFS,所以不能借助通过函数调用所是使用的程序栈。
我们可以手工加一个模拟栈。在Q4中解决了哈希的问题,利用哈希表就能快速访问状态对应的值,在这里,我们把原来的bool值改为char或其他能表示一次操作(至少需要5种状态,除了u r l d 外还要能表示状态已访问)的值就行了。
在搜索到解时,记录下最后一个访问的状态值,然后从读取该状态对应的操作开始,就像栈操作的退栈一样,不停往回搜,直到找到搜索起点为止。记录好栈退出来的操作值,就是一条路径。
ifconfig/gateway ( Tue, 2 Dec 2008 17:13:53 +0800 )
Description: 设置IP和掩码
ifconfig eth0 192.168.5.40 netmask 255.255.255.0
设置IP alias
ifconfig eth0:1 192.168.5.41 netmask 255.255.255.0 up
设置网关
route add default gw 192.168.5.1
tcpdump抓包分析详解收藏 ( Tue, 2 Dec 2008 12:28:59 +0800 )
Description: 说实在的,对於 tcpdump这个软体来说,你甚至可以说这个软体其实就是个骇客软体,因为他不但可以分析封包的流向,连封包的内容也可以进行『监听』,如果你使用的传输资料是明码的话,不得了,在 router 上面就可能被人家监听走了! 很可怕呐!所以,我们也要来了解一下这个软体啊!(注:这个tcpdump 必须使用 root 的身份执行)
[root@linux ~]# tcpdump [-nn] [-i 介面] [-w 储存档名] [-c 次数] [-Ae]
[-qX] [-r 档案] [所欲撷取的资料内容]
参数:
-nn:直接以 IP 及 port number 显示,而非主机名与服务名称
-i :后面接要『监听』的网路介面,例如 eth0, lo, ppp0 等等的介面;
-w :如果你要将监听所得的封包资料储存下来,用这个参数就对了!后面接档名
-c :监听的封包数,如果没有这个参数, tcpdump 会持续不断的监听,
直到使用者输入 [ctrl]-c 为止。
-A :封包的内容以 ASCII 显示,通常用来捉取 WWW 的网页封包资料。
-e :使用资料连接层 (OSI 第二层) 的 MAC 封包资料来显示;
-q :仅列出较为简短的封包资讯,每一行的内容比较精简
-X :可以列出十六进位 (hex) 以及 ASCII 的封包内容,对於监听封包内容很有用
-r :从后面接的档案将封包资料读出来。那个『档案』是已经存在的档案,
并且这个『档案』是由 -w 所制作出来的。
所欲撷取的资料内容:我们可以专门针对某些通讯协定或者是 IP 来源进行封包撷取,
那就可以简化输出的结果,并取得最有用的资讯。常见的表示方法有:
‘host foo’, ‘host 127.0.0.1’ :针对单部主机来进行封包撷取
‘net 192.168’ :针对某个网域来进行封包的撷取;
‘src host 127.0.0.1’ ‘dst net 192.168’:同时加上来源(src)或目标(dst)限制
‘tcp port 21’:还可以针对通讯协定侦测,如 tcp, udp, arp, ether 等
还可以利用 and 与 or 来进行封包资料的整合显示呢!
范例一:以 IP 与 port number 捉下 eth0 这个网路卡上的封包,持续 3 秒
[root@linux ~]# tcpdump -i eth0 -nn
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 96 bytes
01:33:40.41 IP 192.168.1.100.22 > 192.168.1.11.1190: P 116:232(116) ack 1 win 9648
01:33:40.41 IP 192.168.1.100.22 > 192.168.1.11.1190: P 232:364(132) ack 1 win 9648
<==按下 [ctrl]-c 之后结束
6680 packets captured <==捉下来的封包数量
14250 packets received by filter <==由过滤所得的总封包数量
7512 packets dropped by kernel <==被核心所丢弃的封包
如果你是第一次看 tcpdump 的 man page 时,肯定一个头两个大,因为 tcpdump几乎都是分析封包的表头资料,使用者如果没有简易的网路封包基础,要看懂粉难呐! 所以,至少您得要回到网路基础里面去将 TCP封包的表头资料理解理解才好啊! ^_^!至於那个范例一所产生的输出范例中,我们可以约略区分为数个栏位,我们以范例一当中那个特殊字体行来说明一下:
* 01:33:40.41:这个是此封包被撷取的时间,『时:分:秒』的单位;
* IP:透过的通讯协定是 IP ;
* 192.168.1.100.22 > :传送端是 192.168.1.100 这个 IP,而传送的 port number 为 22,您必须要了解的是,那个大於 (>) 的符号指的是封包的传输方向喔!
* 192.168.1.11.1190:接收端的 IP 是 192.168.1.11, 且该主机开启 port 1190 来接收;
* P 116:232(116):这个封包带有 PUSH 的资料传输标志, 且传输的资料为整体资料的 116~232 byte,所以这个封包带有 116 bytes 的资料量;
* ack 1 win 9648:ACK与 Window size 的相关资料。
最简单的说法,就是该封包是由 192.168.1.100 传到 192.168.1.11,透过的 port 是由 22 到 1190 ,且带有116 bytes 的资料量,使用的是 PUSH 的旗标,而不是 SYN 之类的主动连线标志。呵呵!不容易看的懂吧!所以说,上头才讲请务必到TCP 表头资料的部分去瞧一瞧的啊!
再来,一个网路状态很忙的主机上面,你想要取得某部主机对你连线的封包资料而已时, 使用tcpdump 配合管线命令与正规表示法也可以,不过,毕竟不好捉取! 我们可以透过 tcpdump的表示法功能,就能够轻易的将所需要的资料独立的取出来。在上面的范例一当中,我们仅针对 eth0 做监听,所以整个 eth0介面上面的资料都会被显示到萤幕上,不好分析啊!那麼我们可以简化吗?例如只取出 port 21 的连线封包,可以这样做:
[root@linux ~]# tcpdump -i eth0 -nn port 21
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 96 bytes
01:54:37.96 IP 192.168.1.11.1240 > 192.168.1.100.21: . ack 1 win 65535
01:54:37.96 IP 192.168.1.100.21 > 192.168.1.11.1240: P 1:21(20) ack 1 win 5840
01:54:38.12 IP 192.168.1.11.1240 > 192.168.1.100.21: . ack 21 win 65515
01:54:42.79 IP 192.168.1.11.1240 > 192.168.1.100.21: P 1:17(16) ack 21 win 65515
01:54:42.79 IP 192.168.1.100.21 > 192.168.1.11.1240: . ack 17 win 5840
01:54:42.79 IP 192.168.1.100.21 > 192.168.1.11.1240: P 21:55(34) ack 17 win 5840
瞧!这样就仅提出 port 21 的资讯而已,且仔细看的话,你会发现封包的传递都是双向的, client 端发出『要求』而 server 端则予以『回应』,所以,当然是有去有回啊! 而我们也就可以经过这个封包的流向来了解到封包运作的过程。 举例来说:
1. 我们先在一个终端机视窗输入『 tcpdump -i lo -nn 』 的监听,
2. 再另开一个终端机视窗来对本机 (127.0.0.1) 登入『ssh localhost』
那麼输出的结果会是如何?
[root@linux ~]# tcpdump -i lo -nn
1 tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
2 listening on lo, link-type EN10MB (Ethernet), capture size 96 bytes
3 11:02:54.253777 IP 127.0.0.1.32936 > 127.0.0.1.22: S 933696132:933696132(0)
win 32767 <mss 16396,sackOK,timestamp 236681316 0,nop,wscale 2>
4 11:02:54.253831 IP 127.0.0.1.22 > 127.0.0.1.32936: S 920046702:920046702(0)
ack 933696133 win 32767 <mss 16396,sackOK,timestamp 236681316 236681316,nop,
wscale 2>
5 11:02:54.253871 IP 127.0.0.1.32936 > 127.0.0.1.22: . ack 1 win 8192 <nop,
nop,timestamp 236681316 236681316>
6 11:02:54.272124 IP 127.0.0.1.22 > 127.0.0.1.32936: P 1:23(22) ack 1 win 8192
<nop,nop,timestamp 236681334 236681316>
7 11:02:54.272375 IP 127.0.0.1.32936 > 127.0.0.1.22: . ack 23 win 8192 <nop,
nop,timestamp 236681334 236681334>
上表显示的头两行是 tcpdump 的基本说明,然后:
* 第 3 行显示的是『来自 client 端,带有 SYN 主动连线的封包』,
* 第 4 行显示的是『来自 server 端,除了回应 client 端之外(ACK),还带有 SYN 主动连线的标志;
* 第 5 行则显示 client 端回应 server 确定连线建立 (ACK)
* 第 6 行以后则开始进入资料传输的步骤。
从第 3-5 行的流程来看,熟不熟悉啊?没错!那就是 三向交握 的基础流程啦!够有趣吧! 不过 tcpdump之所以被称为骇客软体之一可不止上头介绍的功能呐! 上面介绍的功能可以用来作为我们主机的封包连线与传输的流程分析,这将有助於我们了解到封包的运作,同时了解到主机的防火墙设定规则是否有需要修订的地方。
更神奇的使用要来啦!如果我们使用tcpdump 在 router 上面监听『明码』的传输资料时, 例如 FTP 传输协定,你觉得会发生什麼问题呢? 我们先在主机端下达『tcpdump -i lo port 21 -nn -X 』然后再以 ftp 登入本机,并输入帐号与密码, 结果你就可以发现如下的状况:
[root@linux ~]# tcpdump -i lo -nn -X ‘port 21’
0x0000: 4500 0048 2a28 4000 4006 1286 7f00 0001 E..H*(@.@…….
0x0010: 7f00 0001 0015 80ab 8355 2149 835c d825 ………U!I.\.%
0x0020: 8018 2000 fe3c 0000 0101 080a 0e2e 0b67 …..<………g
0x0030: 0e2e 0b61 3232 3020 2876 7346 5450 6420 …a220.(vsFTPd.
0x0040: 322e 302e 3129 0d0a 2.0.1)..
0x0000: 4510 0041 d34b 4000 4006 6959 7f00 0001 E..A.K@.@.iY….
0x0010: 7f00 0001 80ab 0015 835c d825 8355 215d ………\.%.U!]
0x0020: 8018 2000 fe35 0000 0101 080a 0e2e 1b37 …..5………7
0x0030: 0e2e 0b67 5553 4552 2064 6d74 7361 690d …gUSER.dmtsai.
0x0040: 0a .
0x0000: 4510 004a d34f 4000 4006 694c 7f00 0001 E..J.O@.@.iL….
0x0010: 7f00 0001 80ab 0015 835c d832 8355 217f ………\.2.U!.
0x0020: 8018 2000 fe3e 0000 0101 080a 0e2e 3227 …..>……..2′
0x0030: 0e2e 1b38 5041 5353 206d 7970 6173 7377 …8PASS.mypassw
0x0040: 6f72 6469 7379 6f75 0d0a ordisyou..
上面的输出结果已经被简化过了,你必须要自行在你的输出结果当中搜寻相关的字串才行。 从上面输出结果的特殊字体中,我们可以发现『该 FTP软体使用的是 vsftpd ,并且使用者输入 dmtsai 这个帐号名称,且密码是 mypasswordisyou』嘿嘿!你说可不可怕啊!如果使用的是明码的方式来传输你的网路资料? 所以我们才常常在讲啊,网路是很不安全低!
另外你得了解,为了让网路介面可以让 tcpdump 监听,所以执行 tcpdump 时网路介面会启动在 『错乱模式 (promiscuous)』,所以你会在/var/log/messages 里面看到很多的警告讯息,通知你说你的网路卡被设定成为错乱模式!别担心,那是正常的。 至於更多的应用,请参考man tcpdump 罗!
查找算法:
1. 无序表的顺序查找*******\n" 2. 有序表的折半查找*******\n" 3. 二叉顺序树的查找*
#include<iostream.h> #include<iomanip.h> #include<malloc.h> #define N 100 typedef int elemtype; typedef elemtype elem[N]; typedef struct node { elemtype data; struct node*left,*right; }BTree; int search(elemtype elem[],int key,int length)//顺序查找 { int i=0; while(i<length&&elem[i]!=key) i++; if(i>=length) return -1; else return i; } int search_bin(elemtype elem[],int low,int high,int key)//折半查找 { int mid; if(low>high) return -1;//not find return -1 else { mid=(low+high)/2; if(key<elem[mid]) return (search_bin(elem,low,mid-1,key)); else if(key==elem[mid]) return mid;//find! else return (search_bin(elem,mid+1,high,key)); } } BTree*tree(BTree*&r,elemtype x) { if(r==NULL) { r=new BTree; r->data=x; r->left=r->right=NULL; } else { if(x<r->data) tree(r->left,x); else tree(r->right,x); } return r; } void incorder(BTree*t) { if(t!=NULL) { incorder(t->left); cout<<setw(3)<<t->data; incorder(t->right); } } int remove(BTree*&b,int x)//查找并删除 { BTree*p,*q,*r,*t; p=b; //p指向待比较的接点 q=NULL; //q指向p的前驱接点 while(p!=NULL&&p->data!=x) if(x<p->data) { q=p; p=p->left; } else { q=p; p=p->right; } if(p==NULL) return 0; else if(p->left==NULL)//被删接点无左子树 { if(q==NULL) t=p->right; else if(q->left==p) q->left=p->right; else q->right=p->right; } else//有左子树 { r=p->left; while(r->right!=NULL) r=r->right; r->right=p->right; if(q==NULL) t=p->left;//被删接点是根接点 else if(q->left==p) q->left=p->left; else q->right=p->left; } return 1; } void printree(BTree*b) { if(b!=NULL) { cout<<b->data; if(b->left!=NULL||b->right!=NULL) { cout<<"("; printree(b->left); if(b->right!=NULL) cout<<","; printree(b->right); cout<<")"; } } } void main() { BTree *root=NULL; elemtype elem[N]; int ch,key,length,n1,i,flag; cout<<"##A simple find example!!##\n"; cout<<"**********************************\n" <<"********1. 无序表的顺序查找*******\n" <<"********2. 有序表的折半查找*******\n" <<"********3. 二叉顺序树的查找*******\n" <<"********4. 退出!! *******\n"; cout<<"put in the excel's length(<=100):\n"; cin>>length; cout<<"put in your "<<length<<" nums elem:\n"; for(i=0;i<length;i++) cin>>elem[i]; cout<<"elem数组"<<endl<<"下标"; for(i=0;i<length;i++) cout<<setw(3)<<i; cout<<endl<<" 值:"; for(i=0;i<length;i++) cout<<setw(3)<<elem[i]; cout<<endl; while(flag) { cout<<"put in your choose(1~4):"; cin>>ch; if(ch==1) { cout<<"put in the elem you want to find:"; cin>>key; n1=search(elem,key,length); if(n1>=0) cout<<"elem["<<n1<<"]="<<key<<endl; else cout<<key<<" is not find!!\n"; flag=1; } else if(ch==2) { for(i=0;i<length;i++) { for(int j=0;j<length;j++) { if(elem[i]<elem[j]) { int text; text=elem[i]; elem[i]=elem[j]; elem[j]=text; } } } cout<<"排序后:\n"; cout<<"elem数组"<<endl<<"下标"; for(i=0;i<length;i++) cout<<setw(3)<<i; cout<<endl<<" 值:"; for(i=0;i<length;i++) cout<<setw(3)<<elem[i]; cout<<endl; cout<<"put in the elem you want to find:"; cin>>key; n1=search_bin(elem,0,length-1,key); if(n1>=0) cout<<"elem["<<n1<<"]="<<key<<endl; else cout<<key<<" is not find!!\n"; flag=1; } else if(ch==3) { for(i=0;i<length;i++) root=tree(root,elem[i]); cout<<"二叉排序树:\n"; printree(root);cout<<endl; cout<<"递增序列:\n"; incorder(root); cout<<endl; cout<<"put in the elem you want to delete:"; cin>>key; remove(root,key); cout<<"删除后的二叉排序树:\n"; printree(root); cout<<endl; flag=1; } else if(ch==4) {flag=0;cout<<"退出!!!\n";} else cout<<"算法完成,谢谢使用再见!!\n"; } }