论坛交流
首页办公自动化| 网页制作| 平面设计| 动画制作| 数据库开发| 程序设计| 全部视频教程
应用视频: Windows | Word2007 | Excel2007 | PowerPoint2007 | Dreamweaver 8 | Fireworks 8 | Flash 8 | Photoshop cs | CorelDraw 12
编程视频: C语言视频教程 | HTML | Div+Css布局 | Javascript | Access数据库 | Asp | Sql Server数据库Asp.net  | Flash AS
当前位置 > 文字教程 > C语言程序设计教程
Tag:新手,函数,指针,数据类型,对象,Turbo,入门,运算符,数组,结构,二级,,tc,游戏,试题,问答,编译,视频教程

几种算法

文章类别:C语言程序设计 | 发表日期:2008-9-24 14:45:30

【题目】已知一棵二叉树按顺序方式存储在数组 A[1..n]中。设计算法求出下标分别为 i 和 j 的两个结点的最近的公共祖先结点的值。

【来源】武汉大学2000年第五(1)题(8’)

【解答】
#inlcude <stdio.h>
parent(int A[],int i,int j)
{int k,m;
m=k=0;
if(i==1||j==1||A[i]==0||A[j]==0||i==j) // A[i]==0或A[j]==0表示不存在该结点
{printf("Error.\n");return;}
while(i!=1&&j!=1){
if(i<j){j=j/2;m=1;} // 结点 j 的最近祖先结点是 ┗ j/2 ┛
else if(j<i){i=i/2;k=1;}
else if(j==i){i=j;break;} // i=j 表示找到共同祖先
}
if(j==1||i==1)i=1; // i 或 j 有一个到了根结点
else if(m==0||k==0)i=i/2; // m、k 中有一个为 0 ,说明在查找过程中 i 或 j 有一个没移动
printf("The nearest common parent is A[%d].\n",i);
}


【分析】本题思路是使 i 和 j 交替追赶,直到相等。

------------------------------------------------------------------------

【题目】试写一个判别给定二叉树是否为二叉排序树的算法。

【来源】长沙铁道学院98年第五(1)题(12’)

【解答】
typedef struct node{
char data;
struct node *left,*right;
}*T;

issorttree(T)
{
initqueue(Q); // 初始化队列
inqueue(Q,T); // 树根结点进队列
while(!empty(Q)){
outqueue(Q,T);
if(T->data>T->left->data&&T->data<T->right->data){
if(T->left)inqueue(Q,T->left);
if(T->right)inqueue(Q,T->right);
}
else if(T->left||T->right)return 1; // 不符合二叉排序树的特征,则终止并返回‘ 1 ’
}
return 0; // 是二叉排序树,则返回 ‘0’
}

【分析】注重队列的运用,其他如图的广度搜索(教材《清华 C 版》)。

------------------------------------------------------------------------------

【题目】设一单向链表的头指针为head,链表的记录中包含着整数类型的key域,试设计算法,
将此链表的记录按照key递增的次序进行就地排序.(不答应使用数组做辅助存储)

【来源】中科院计算机技术研究所1999年第五(1)题 (10’)
华中理工大学2000年第八(2)题 (13’)

【解答】
typedef struct CircleList{ // 定义循环链表
int key;
struct CircleList *next;
}*listnode;

ListSort(listnode head)
{
int k,min,i,j;
listnode p,q,t;
p=head->next;
while(p->next!=head->next){p=p->next;k+=1;} // 统计链表中元素个数,保存在 K 中
p=head;j=1;
for(i=1;i<k;i++){
while(j<=i){p=p->next;j++;} // 找应填入当前最小元素的位置,该位置保存在 q 中
min=p->key;q=p; // 将当前位置元素的值设为初始最小值
while(p->next!=head->next){
if(min>p->key){min=p->key;t=p;} // 找当前最小元素,并保存在 t 所指位置中
p=p->next;
}
t->key=q->key;q->key=min;j=1; // 交换 q 位置元素和最小元素的值
}
}

【分析】本题不需要修改链表中的各个指针。

--------------------------------------------------------------------------

前几天根据快速排序 Quick Sort算法的基本思想,编写了如下分治策略的算法,供大家讨论: 
思路: 
设输入的序列L[p..r],确定支点元素l[p]和l[r],并使l[p].key<=l[r].key 
分解(Divide):将序列L[p..r]划分成三个子序列L[p..k-1]、L[k+1..m-1]和L[m+1..r],使L[p..q]中关系为L[p..k-1]、l[k]、L[k+1..m-1]、l[m]、L[m+1..r]任一区间元素的值不大于其后区间元素的值。 
递归求解(Conquer):通过递归调用快速排序算法分别对L[p..k-1]、L[k+1..m-1]和L[m+1..r]进行排序。 
算法的实现(用C语言实现) 
QSort(Sqlist &L,int low,int high){ 
c=high-low; /*循环次数*/ 
if(c<10)直接调用插入排序法; /*小数时直接调用插入排序法*/ 
if(L.r[low].key>L.r[high].key)L.r[low]<->L.r[high]; /*确保区间内第一个元素的值不大于区间内最后一个元素的值*/ 
ilow=low; /*小于区间内第一个元素的值数组边界下标*/ 
ihigh=high; /*大于区间内最后一个元素的值数组边界下标*/ 
for(i=low+1;i<c;i++){ 
if(L.r[i].key<L.r[low].key)L.r[i]<->L.r[++ilow]; /*小于区间内第一个元素的值放置ilow区间内*/ 
else 
if(L.r[i].key>L.r[high].key){ 
L.r[i]<->L.r[--ihigh]; /*大于区间内最后一个元素的值放置ihigh区间内*/ 
i--; /*下一个比较位置不变*/ 
c--; /*循环次数减一*/ 


L.r[ilow]<->L.r[low]; /*将小于区间内第一个元素的边界下标元素与第一个元素互换*/ 
L.r[ihigh]<->L.r[high]; /*将大于区间内最后一个元素的边界下标元素与最后一个元素互换*/ 
QSort(L,low,ilow-1); 
QSort(L,ilow+1,ihigh-1); 
QSort(L,ihigh+1,high); 

void QuickSort(Sqlist &L) 

QSort(L,1,L.length); 

优点: 
1、每次快速排序将确定二个元素位置 
2、每次快速排序将划分三个区间,优化后续平均时间和空间复杂度 
缺点: 
1、存在较多的元素交换(每次需要三步),不及改进快速排序法利用空穴赋值方便

--------------------------------------------------------------------------------------

四阶Runge-Kutta法 

一阶常微分方程: 
u'=f(t,u) a<t<b
u(t(0))=u(0) 


Runge-Kutta非线性高阶单步法,p阶R-K法的整体阶段误差为O(h^p)

R-K四阶算法:
u(i+1)=u(i)+h*(k1+3*k2+3*k3+k4)/8
k1=f(t(i),u(i))
k2=f(t(i+h/3),u(i+h*k1/3))
k3=f(t(i+h/3),u(i+h*k2/3))
k4=f(t(i+h),u(i+h*k3)) 

四阶程序如下:
class RK{
private:
double k1,k2,k3,k4;
double h,b,u,a;
public:
void seth(double l=0){h=l;} //设步长
void setf(double xa=0,double xb=0,double y=0) //设初值和范围(xa,xb)
{
b=xb;
a=xa;
u=y;
}
double f(double t,double u) //函数值,修改它以适应各自需要
{
//函数设定
double f=u-2*t/u; 
return f;
}
void dork() //R-K 主函数
{
for(int count=0;count<(b-a)/h;count++)
{
k1=f(a+count*h,u);
k2=f(a+count*h+h/3,u+h*k1/3);
k3=f(a+count*h+2*h/3,u-h*k1/3+h*k2);
k4=f(a+count*h+h,u+h*k1-h*k2+h*k3);
u=u+h*(k1+3*k2+3*k3+k4)/8;
count<<u<<endl;
}

}
}; 

void main()
{
RK my;
my.seth(0.1);
my.setf(0,1,1);
my.dork();
}

--------------------------------------------------------------------------------------
快速排序 

算法的基本思想:

快速排序的基本思想是基于分治策略的。对于输入的子序列ap..ar,假如规模足够小则直接进行排序,否则分三步处理:

分解(Divide):将输入的序列ap..ar划分成两个非空子序列ap..aq和aq+1..ar,使ap..aq中任一元素的值不大于aq+1..ar中任一元素的值。 
递归求解(Conquer):通过递归调用快速排序算法分别对ap..aq和aq+1..ar进行排序。 
合并(Merge):由于对分解出的两个子序列的排序是就地进行的,所以在ap..aq和aq+1..ar都排好序后不需要执行任何计算ap..ar就已排好序。 
这个解决流程是符合分治法的基本步骤的。因此,快速排序法是分治法的经典应用实例之一。

算法Quick_Sort的实现:

Pascal实现:
Procedure Quick_Sort(p,r:TPosition;var L:TList); {快速排序}

var

q:TPosition;

begin

if L[p..r]足够小 then Sort(p,r,L) {若L[p..r]足够小则直接对L[p..r]排序}

else

begin

q:=Partition(p,r,L); {将L[p..r]分解为L[p..q]和L[q+1..r]两部分}

Quick_Sort(p,q,L); {递归排序L[p..q]}

Quick_Sort(q+1,r,L); {递归排序L[q+1..r]}

end;

end;

--------------------------------------------------------------------------------------
【题目】设中序线索二叉树的结点结构为:|left|ltag|data|rtag|right|. 现已知中序线索
二叉树的根结点地址root。设计一程序,打印出该线索二叉树的中序遍历结果.不得
再使用O(n)级的辅存空间。

【来源】上海交通大学96年第十题(15') 

【解答】intravers(root:bitree)
finished:=false;t:=root;
while not finished do

while t↑.ltag=0 do t:=t↑.lch // 左孩子不空
write(t↑.data); // 访问左孩子
if t↑.rtag=1 then 
【t:=t↑.rch;{后继结点}
write(t↑.data);{访问当前根结点}
t:=t↑.rch{访问当前根结点的右孩子}

else 
t:=t↑.rch; // 右孩子不空
if t↑.rch=nil then finished:=true


--------------------------------------------------------------------
【题目】请设计一个算法,把二叉树的叶子结点按从左到右的顺序连成一个单链表。二叉树
按lchild - rchild 的方式存储,链接时用叶子结点的rchild 域存放指针。

【来源】长沙铁道学院99年第四(3)题(8’)

【解答】
#include <stdio.h>
#include <alloc.h>
#include <string.h>
#include <ctype.h>
typedef struct node{
char c;
int count;
struct node *left,*right;
}bnode;
bnode *k,*m; // 分别记录叶子链表的第一及当前结点的前驱

bnode *creatree(bnode *ptr,char ch) // 建二叉树
{ int result;bnode *p,*r; // p 指向当前结点的最近的父结点
p=NULL;r=ptr;
while(r){ // 检查是否存在相同结点
result=(int)(r->c)-(int)(ch);
if(!result){r->count+=1;return r;}
else
{p=r;
r=result<0?r->right:r->left;
}
}
r=(struct node *)malloc(sizeof(bnode));// 建新结点
r->left=r->right=NULL;
r->c=(char)malloc(sizeof(char));
r->c=ch;r->count=1;
if(!ptr)return r;
else if(result>0) p->left=r;
else p->right=r;
return r;
}


leaflink(bnode *root)
{
if(!root)return;
if(root->left==NULL&&root->right==NULL){
if(k==NULL)k=m=root; // 保存找到的第一个叶子结点(k指针)
else {m->right=root;m=m->right;}}
if(root->left)leaflink(root->left);
if(root->right)leaflink(root->right);
return;
}


main()
{
char *s;
bnode *root=NULL;
printf("Input a string:");
scanf("%s",s);
do{
if(!root)root=creatree(root,*s);
else creatree(root,*s);
s+=1;
}while(*s);
leaflink(root); // 将叶结点链成链表
while(k){printf("%c",k->c);k=k->right;} // 输出该链表
}


【分析】建一个一般的二叉树假如采用递归的话,将对调用该二叉树造成不便;相反,采用
非递归建一个二叉排序树既简单又实用。
---------------------------------------------------------------------------------- 

【题目】在二叉树中查找值为x的结点,试编写算法打印值为x的结点的所有祖先,假设值为x
的结点不多于一个。分析算法的时间复杂度(不加分析直接写出结果得零分)。

【来源】上海交通大学98年第五题(15')

【解答】proc searchx(p:bitree)
setnull(s);found:=false;
count:=0;
lflag[1..n]:=0;{lflag、rflag分别保存结点p的左右孩子是否已访问}
rflag[1..n]:=0;{初始状态为未被访问}
while not found and count<=n do
【if p↑.data=x then found:=true;{退出}
if lflag[p]=0 and p↑.left<>nil then 
【push(s,p);count:=count+1;lflag[p]=1;p:=p↑.left;】
else
【if rflag[p]=0 and p↑.right<>nil then
【push(s,p);count:=count+1;rflag[p]=1;p:=p↑.right;】 
else pop(s,p)
】 

if found then
【while not empty(s) do【pop(s,p);write(p↑.data)】{打印祖先结点}】
else
write("error") 
endp;

【时间复杂度分析】本题实际上是先序遍历的一个应用,因此算法的复杂度和先序遍历相同,
为O(n).
-----------------------------------------------------------------------------------
用栈和递归求解两顶点的所有简单路径

栈和递归在程序设计中的应用是非常广的,比如对于迷宫的求解、表达式的求解,等都可以用栈来解决,典型的hanoi塔问题,树和图的遍历等都可以用递归来解决,在数据结构等很多书中都有介绍,假如对此不了解,可去参考。递归算法的设计实际上就是对问题的抽象的过程,假如抽象到每个小问题都有相同特征时,那就形成了递归,递归算法简明易懂,下面我就介绍一种利用到栈与递归求解两顶点所有简单路径的问题。

  先说明一下什么叫“两顶点的所有简单路径”的概念,“两顶点的所有简单路径”的意思通熟的讲就是在图中有若干点,点与点之间可有边相连(在数据结构中叫“图”),任意设个起点和终点,现在要求的是从起点到终点所有可能通过的边序列的集合。(注:是简单有序边,也就是说简单路径,所谓简单路径就是序列中顶点不重复出现的路径。)举个例子:从上海到北京,我可以直接到达,也可以先到四川,再到北京,或者先到广东,再到北京,等等。如:上海->四川->上海->北京,这就不是简单路径了,显然,不是简单的路径有无数条。

为了描述方便,我用了简单的二维数组来做存储结构。(当然,完全可以用邻接矩阵、邻接表、十字链表、邻接多重表等来表示)* * *

* * *

* * *

如上图左上角的坐标为(1,1),右上角的坐标为(3,1),左下角的坐标为(1,3),右下角的坐标为(3,3),点与点之间都可相通,假设从点(1,1)要到(3,3),有多少种走法?

对这个问题的求解先要熟悉到它的本质,其本质是一个点有四个方向(各边非凡处理),依次探索四个方向上的点,判定是不是最后的目的地,假如是,则进栈并返回,假如不是则进栈,将此点同样上述处理。显然这个过程是一个递归的过程,直到是最后的目的地,才层层返回。

当然,解决此问题不是只有栈与递归才能解决,经我自己摸索,用生成树的方法访问其双亲结点,也可解决,假如有更好的方法,请多多指教。



注重:当运行程序时,输入的maxx,maxy的数最好小等于3,不然效率很低!

作者:张钧卿

本程序本站没有测试! 

//用栈和递归求解两顶点的所有简单路径
//borland c++ 3.1下编译通过

#include <stdlib.h>
#include <iostream.h>
#define SIZE 100
#define STACK_INIT_SIZE 1
#define STACKINCREMENT 1
typedef struct pos{
int x,y;
}pos;
typedef struct{
pos *base,*top;
int stacksize;
}sqstack;
sqstack s;
pos pstack[SIZE][SIZE];
int m=0,maxx,maxy;

int initstack(sqstack &s){
s.base=(pos *)malloc(STACK_INIT_SIZE*sizeof(pos));
s.top=s.base;
s.stacksize=STACK_INIT_SIZE;
return 1;
}
int push(sqstack &s,pos e){
if(s.top-s.base>=s.stacksize){
s.base=(pos *)realloc(s.base,(s.stacksize+STACKINCREMENT)*sizeof(pos));
s.top=s.base+s.stacksize;
s.stacksize+=STACKINCREMENT;
}
*s.top++=e;
return 1;
}
int pop(sqstack &s,pos &e){
if(s.top==s.base) return 0;
e=*--s.top;
return 1;
}
int stacktraverse(sqstack s,pos e){
int i;
for(i=0;(s.base+i)<=(s.top-1);i++)
if(((s.base+i)->x==e.x) && ((s.base+i)->y==e.y)) return 0;
return 1;
}
void list(int maxx,int maxy,pos a,pos b)
{
int fx,n;pos c;
push(s,a);
if(a.x==b.x && a.y==b.y)
{
for(n=0;(s.base+n)<=(s.top-1);n++) pstack[m][n]=*(s.base+n);m++;
pop(s,c);
return;
}
for(fx=1;fx<=4;fx++)
{
switch(fx)
{
case 1:
{if(a.x<maxx) {c.x=a.x+1;c.y=a.y;break;} else continue;}
case 2:
{if(a.y<maxy) {c.x=a.x;c.y=a.y+1;break;} else continue;}
case 3:
{if(a.x>1) {c.x=a.x-1;c.y=a.y;break;} else continue;}
case 4:
{if(a.y>1) {c.x=a.x;c.y=a.y-1;break;} else continue;}
}
if(stacktraverse(s,c)) list(maxx,maxy,c,b);
}
pop(s,c);
}

main()
{
int i,j;
pos a,b;
cout<<"max x=";
cin>>maxx;
cout<<"max y=";
cin>>maxy;
cout<<"start x=";
cin>>a.x;
cout<<"start y=";
cin>>a.y;
cout<<"end x=";
cin>>b.x;
cout<<"end y=";
cin>>b.y;
initstack(s);
list(maxx,maxy,a,b);
for(i=0;i<=m-1;i++){
for(j=0;j<=SIZE;j++)
{cout<<'('<<pstack[i][j].x<<','<<pstack[i][j].y<<')'<<" ";
if(pstack[i][j].x==b.x && pstack[i][j].y==b.y) break;}
cout<<"\n";}
return 1;


-------------------------------------------------------------------------------
Bubble Sort(冒泡法) 

最简单的排序方法是冒泡排序方法。这种方法的基本思想是,将待排序的元素看作是竖着排列的“气泡”,较小的元素比较轻,从而要往上浮。在冒泡排序算法中我们要对这个“气泡”序列处理若干遍。所谓一遍处理,就是自底向上检查一遍这个序列,并时刻注重两个相邻的元素的顺序是否正确。假如发现两个相邻元素的顺序不对,即“轻”的元素在下面,就交换它们的位置。显然,处理一遍之后,“最轻”的元素就浮到了最高位置;处理二遍之后,“次轻”的元素就浮到了次高位置。在作第二遍处理时,由于最高位置上的元素已是“最轻”元素,所以不必检查。一般地,第i遍处理时,不必检查第i高位置以上的元素,因为经过前面i-1遍的处理,它们已正确地排好序。这个算法可实现如下。




Bubble Sort程序: 


STL C++程序:(VC++6.0通过)
#include "stdafx.h"
#include "iostream.h"

template<class T>
class doit{
private:
int x,y;
T temp;
public: 
doit(T* in,int count)
{
for(y=0;y<count-1;y++)
{
for(x=1;x<count-y;x++)
{
if((*(in+x))>(*(in+x-1)))
{
temp=(*(in+x-1));
(*(in+x-1))=(*(in+x));
(*(in+x))=temp;
}
}
}
}
};

int main()
{
double a[4]={1.1,1.3,1.9,2.2};
doit<double> d(a,4);
for(int i=0;i<4;i++)
{
cout<<a[i]<<endl;
}
return 0;


C语言程序:(TC 2.0通过) 
void doit(float* in,int count)
{
int x;
int y;
float temp;
for(y=0;y<count-1;y++)
{
for(x=1;x<count-y;x++)
{
if((*(in+x))>(*(in+x-1)))
{
temp=(*(in+x-1));
(*(in+x-1))=(*(in+x));
(*(in+x))=temp;
}
}
}
}
------------------------------------------------------------
选择排序(Selection Sort)
选择排序的基本思想是对待排序的记录序列进行n-1遍的处理,第i遍处理是将ai,…,an中最小者与ai交换位置。这样,经过i遍处理之后,前i个记录的位置已经是正确的了。

直接选择排序:
void docr(float *in,int count)
{
int i;
int j;
int x=0;
float temp;
for(i=0;i<count;i++)
{
temp=*(in+i);
x=i;
for(j=i;j<count;j++)
{
if(*(in+j)<temp)
{
temp=*(in+j);
x=j;
}
}
*(in+x)=*(in+i);
*(in+i)=temp;
}
}

总移动次数:3(n-1)
比较次数:n(n-1)/2 
-------------------------------------------------------
如何产生素数

Solovag-Strasson
Robert Solovag和Volker Strasson开发了一种概率的基本测试算法。这个算法使用了雅可比函数来测试p是否为素数:

(1) 选择一个小于p的随机数a。
(2) 假如GCD(a,p)<>1,那么p通不过测试,它是合数。
(3) 计算j=a^(p-1)/2 mod p。
(4) 计算雅可比符号J(a,p)。
(5) 假如j<>J(a,p),那么p肯定不是素数。
(6) 假如j=J(a,p),那麽p不是素数的可能性值多是50%

数a被称为一个证据,假如a不能确定p,p肯定不是素数。假如p是合数。随机数a是证据的概率不小于50%。对a选择t个不同的随机值,重复t次这种测试。p通过所有t次测试后,它是合数的可能性不超过1/2^t。

Lehmann
另一种更简单的测试是由Lehmann独自研究的。下面是它的测试算法:

(1) 选择一个小于p的随机数a。
(2) 计算a^(p-1)/2 mod p
(3) 假如a^(p-1)/2<>1或-1(mod p),那么p肯定不是素数。
(4) 假如a^(p-1)/2=1或-1(mod p),那麽p不是素数的可能性值多是50%

同样,重复t次,那麽p可能是素数所冒的错误风险不超过1/2^t。

Rabin-Miller
这是个很轻易且广泛使用的简单算法,它基于Gary Miller的部分象法,有Michael Rabin发展。事实上,这是在NIST的DSS建议中推荐的算法的一个简化版。 

首先选择一个代测的随机数p,计算b,b是2整除p-1的次数。然后计算m,使得n=1+(2^b)m。

(1) 选择一个小于p的随机数a。
(2) 设j=0且z=a^m mod p
(3) 假如z=1或z=p-1,那麽p通过测试,可能使素数
(4) 假如j>0且z=1, 那麽p不是素数
(5) 设j=j+1。假如j<b且z<>p-1,设z=z^2 mod p,然后回到(4)。假如z=p-1,那麽p通过测试,可能为素数。
(6) 假如j=b 且z<>p-1,不是素数

这个测试较前一个速度快。数a被当成证据的概率为75%。这意味着当迭代次数为t时,它产生一个假的素数所花费的时间不超过1/4^t。实际上,对大多数随机数,几乎99.99%肯定a是证据。

实际考虑:
在实际算法,产生素数是很快的。

(1) 产生一个n-位的随机数p
(2) 设高位和低位为1(设高位是为了保证位数,设低位是为了保证位奇数)
(3) 检查以确保p不能被任何小素数整除:如3,5,7,11等等。有效的方法是测试小于2000的素数。使用字轮方法更快
(4) 对某随机数a运行Rabin-Miller检测,假如p通过,则另外产生一个随机数a,在测试。选取较小的a值,以保证速度。做5次 Rabin-Miller测试假如p在其中失败,从新产生p,再测试。


在Sparc II上实现: 2 .8秒产生一个256位的素数
24.0秒产生一个512位的素数
2分钟产生一个768位的素数
5.1分钟产生一个1024位的素数
视频教程列表
文章教程搜索
 
C语言程序设计推荐教程
C语言程序设计热门教程