zhangjjwm

导航

统计

公告

2007年7月2日 #

路径,文件,目录,I/O常见操作汇总(转)

posted @ 2007-07-02 14:48 星空下的悠云 阅读(104) 评论(0) 编辑

2006年7月5日 #

c#2.0中新增的两个压缩类

.NET Framework 2.0 中新增的两个压缩类

System.IO.Compression 命名空间 
 注意:此命名空间在 .NET Framework 2.0 版中是新增的。
System.IO.Compression 命名空间包含提供基本的流压缩和解压缩服务的类。
(downmoon原作)
  类                               说明
 DeflateStream         提供用于使用 Deflate 算法压缩和解压缩流的方法和属性。
 GZipStream             提供用于压缩和解压缩流的方法和属性。
  枚举                         说明
 CompressionMode 指定是否压缩或解压缩基础流。

下面以 GZipStream  为例说明


注意:此类在 .NET Framework 2.0 版中是新增的。

提供用于压缩和解压缩流的方法和属性。
命名空间:System.IO.Compression
程序集:System(在 system.dll 中)
语法
Visual Basic(声明)
Public Class GZipStream
    Inherits Stream
 Visual Basic(用法)
Dim instance As GZipStream
 
C#
public class GZipStream : Stream
 
C++
public ref class GZipStream : public Stream
 
J#
public class GZipStream extends Stream
 
JScript
public class GZipStream extends Stream
 

备注
此类表示 GZip 数据格式,它使用无损压缩和解压缩文件的行业标准算法。这种格式包括一个检测数据损坏的循环冗余校验值。GZip 数据格式使用的算法与 DeflateStream 类的算法相同,但它可以扩展以使用其他压缩格式。这种格式可以通过不涉及专利使用权的方式轻松实现。gzip 的格式可以从 RFC 1952“GZIP file format specification 4.3(GZIP 文件格式规范 4.3)GZIP file format specification 4.3(GZIP 文件格式规范 4.3)”中获得。此类不能用于压缩大于 4 GB 的文件。

给继承者的说明 当从 GZipStream 继承时,必须重写下列成员:CanSeek、CanWrite 和 CanRead。


下面提供 一个完整的压缩与解压类(downmoon原作 ):

 class clsZip
    {
        public void CompressFile ( string sourceFile, string destinationFile )
        {
            // make sure the source file is there
            if ( File.Exists ( sourceFile ) == false )
                throw new FileNotFoundException ( );

            // Create the streams and byte arrays needed
            byte[] buffer = null;
            FileStream sourceStream = null;
            FileStream destinationStream = null;
            GZipStream compressedStream = null;

            try
            {
                // Read the bytes from the source file into a byte array
                sourceStream = new FileStream ( sourceFile, FileMode.Open, FileAccess.Read, FileShare.Read );

                // Read the source stream values into the buffer
                buffer = new byte[sourceStream.Length];
                int checkCounter = sourceStream.Read ( buffer, 0, buffer.Length );

                if ( checkCounter != buffer.Length )
                {
                    throw new ApplicationException ( );
                }

                // Open the FileStream to write to
                destinationStream = new FileStream ( destinationFile, FileMode.OpenOrCreate, FileAccess.Write );

                // Create a compression stream pointing to the destiantion stream
                compressedStream = new GZipStream ( destinationStream, CompressionMode.Compress, true );

                // Now write the compressed data to the destination file
                compressedStream.Write ( buffer, 0, buffer.Length );
            }
            catch ( ApplicationException ex )
            {
                MessageBox.Show ( ex.Message, "压缩文件时发生错误:", MessageBoxButtons.OK, MessageBoxIcon.Error );
            }
            finally
            {
                // Make sure we allways close all streams
                if ( sourceStream != null )
                    sourceStream.Close ( );

                if ( compressedStream != null )
                    compressedStream.Close ( );

                if ( destinationStream != null )
                    destinationStream.Close ( );
            }
        }

        public void DecompressFile ( string sourceFile, string destinationFile )
        {
            // make sure the source file is there
            if ( File.Exists ( sourceFile ) == false )
                throw new FileNotFoundException ( );

            // Create the streams and byte arrays needed
            FileStream sourceStream = null;
            FileStream destinationStream = null;
            GZipStream decompressedStream = null;
            byte[] quartetBuffer = null;

            try
            {
                // Read in the compressed source stream
                sourceStream = new FileStream ( sourceFile, FileMode.Open );

                // Create a compression stream pointing to the destiantion stream
                decompressedStream = new GZipStream ( sourceStream, CompressionMode.Decompress, true );

                // Read the footer to determine the length of the destiantion file
                quartetBuffer = new byte[4];
                int position = (int)sourceStream.Length - 4;
                sourceStream.Position = position;
                sourceStream.Read ( quartetBuffer, 0, 4 );
                sourceStream.Position = 0;
                int checkLength = BitConverter.ToInt32 ( quartetBuffer, 0 );

                byte[] buffer = new byte[checkLength + 100];

                int offset = 0;
                int total = 0;

                // Read the compressed data into the buffer
                while ( true )
                {
                    int bytesRead = decompressedStream.Read ( buffer, offset, 100 );

                    if ( bytesRead == 0 )
                        break;

                    offset += bytesRead;
                    total += bytesRead;
                }

                // Now write everything to the destination file
                destinationStream = new FileStream ( destinationFile, FileMode.Create );
                destinationStream.Write ( buffer, 0, total );

                // and flush everyhting to clean out the buffer
                destinationStream.Flush ( );
            }
            catch ( ApplicationException ex )
            {
                MessageBox.Show(ex.Message, "解压文件时发生错误:", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
            finally
            {
                // Make sure we allways close all streams
                if ( sourceStream != null )
                    sourceStream.Close ( );

                if ( decompressedStream != null )
                    decompressedStream.Close ( );

                if ( destinationStream != null )
                    destinationStream.Close ( );
            }

        }
    }

作者Blog:http://blog.csdn.net/downmoon/

posted @ 2006-07-05 14:16 星空下的悠云 阅读(158) 评论(0) 编辑

2006年6月29日 #

NET Framework Performances Practices

posted @ 2006-06-29 14:27 星空下的悠云 阅读(232) 评论(0) 编辑

2006年6月27日 #

二叉树非递归遍历(C#)

前序:

  private void PreTraWithoutRecurtion(TreeNode tn, ListView lv)
  {
   Stack stack = new Stack();
   
   while(tn.Nodes.Count != 0)
   {
    lv.Items.Add(tn.Text);
    stack.Push(tn);
    tn = tn.Nodes[0];
   }
   lv.Items.Add(tn.Text);
   while(stack.Count != 0)
   {
    tn = (TreeNode)stack.Pop();
    if(tn.Nodes.Count > 1)
    {
     tn = tn.Nodes[1];
     while(tn.Nodes.Count != 0)
     {
      lv.Items.Add(tn.Text);
      stack.Push(tn);
      tn = tn.Nodes[0];
     }
     lv.Items.Add(tn.Text);
    }
    
   }
   
  }

 

中序:

  private void InTraWithoutRecursion(TreeNode tn, ListView lv)
  {
   Stack stack = new Stack();
   while(tn.Nodes.Count != 0)
   {
    stack.Push(tn);
    tn = tn.Nodes[0];
   }

   lv.Items.Add(tn.Text);
   
   while(stack.Count != 0)
   {
    tn = (TreeNode)stack.Pop();
    lv.Items.Add(tn.Text);
    if(tn.Nodes.Count > 1)
    {
     tn = tn.Nodes[1];
     while(tn.Nodes.Count != 0)
     {
      stack.Push(tn);
      tn = tn.Nodes[0];
     }

     lv.Items.Add(tn.Text);
    }
    else if(stack.Count != 0)
     {
      tn = (TreeNode)stack.Pop();
      lv.Items.Add(tn.Text);
      if(tn.Nodes.Count >1)
      {
       tn = tn.Nodes[1];
       while(tn.Nodes.Count != 0)
       {
        stack.Push(tn);
        tn = tn.Nodes[0];
       }

       lv.Items.Add(tn.Text);
      }
     }
   }
  }

后序待续。。。

后序来了:

其实感觉所谓程序就是解决问题思路的“再现”,怎么想的怎么写基本上就差不多了,所以关键怎么想...

always thinking....

  /// <summary>
  /// 非递归后序遍历二叉树
  /// 注:在语句上方的注释为其下面的程序块的说明
  ///     在语句后面的为该语句的说明
  /// =Color.Red 表示从右进入
  /// =Color.Lavender表示从左进入
  /// ==Color.Red 表示从右回来
  /// </summary>
  /// <param name="tn">当前子树的根</param>
  /// <param name="lv">保存结果的列表</param>
  private void PostTraWithoutRecursion(TreeNode tn, ListView lv)
  {
   Stack stack = new Stack();
   
   //找到最左下节点
   while(tn.Nodes.Count != 0)
   {
    tn.BackColor = Color.Lavender;
    stack.Push(tn);
    tn = tn.Nodes[0];
   }
   tn.BackColor = Color.Red;
   lv.Items.Add(tn.Text);
   tn.BackColor = Color.Empty;
   

   //开始其他节点访问
   while(true)
   {
    //指向栈顶节点
    if(stack.Count != 0)
     tn = (TreeNode)stack.Peek();
    else
     return;
    //访问从右子节点回来的点
    while(tn.BackColor == Color.Red)
    {
     lv.Items.Add(tn.Text);
     tn.BackColor = Color.Empty;
     stack.Pop();//访问完毕弹出
     if(stack.Count != 0)//指向栈顶节点
     {
      tn = (TreeNode)stack.Peek();
     }
     else
      return;
    
    }
    //从右子节点进入
    tn.BackColor = Color.Red;
    if(tn.Nodes.Count > 1)
    {
     tn = tn.Nodes[1];//指向右子节点
     //找到右子节点的最左下节点
     while(tn.Nodes.Count != 0)
     {
      tn.BackColor = Color.Lavender;
      stack.Push(tn);
      tn = tn.Nodes[0];
     }
     //访问最左下
     tn.BackColor = Color.Red;
     lv.Items.Add(tn.Text);
     tn.BackColor = Color.Empty;

    }
    //没有右子节点,访问之
    else
    {
     lv.Items.Add(tn.Text);
     tn.BackColor = Color.Empty;

     stack.Pop();
    }
   }
  }

posted @ 2006-06-27 15:43 星空下的悠云 阅读(905) 评论(0) 编辑

如何用栈实现递归与非递归的转换

如何用栈实现递归与非递归的转换

一.为什么要学习递归与非递归的转换的实现方法?
   1)并不是每一门语言都支持递归的.
   2)有助于理解递归的本质.
   3)有助于理解栈,树等数据结构.

二.递归与非递归转换的原理.
   递归与非递归的转换基于以下的原理:所有的递归程序都可以用树结构表示出来.需要说明的是,
这个"原理"并没有经过严格的数学证明,只是我的一个猜想,不过在至少在我遇到的例子中是适用的.
   学习过树结构的人都知道,有三种方法可以遍历树:前序,中序,后序.理解这三种遍历方式的递归和非
递归的表达方式是能够正确实现转换的关键之处,所以我们先来谈谈这个.需要说明的是,这里以特殊的
二叉树来说明,不过大多数情况下二叉树已经够用,而且理解了二叉树的遍历,其它的树遍历方式就不难
了.
1)前序遍历

a)递归方式:
void preorder_recursive(Bitree T)		/* 先序遍历二叉树的递归算法 */

{
if (T) {
visit(T);  /* 访问当前结点 */
preorder_recursive(T->;lchild); /* 访问左子树 */
preorder_recursive(T->;rchild); /* 访问右子树 */
}
}


b)非递归方式
void preorder_nonrecursive(Bitree T)		/* 先序遍历二叉树的非递归算法 */

{
initstack(S);
push(S,T);  /* 根指针进栈 */
while(!stackempty(S)) {
while(gettop(S,p)&&p) { /* 向左走到尽头 */
visit(p); /* 每向前走一步都访问当前结点 */
push(S,p->;lchild);
}
pop(S,p);
if(!stackempty(S)) { /* 向右走一步 */
pop(S,p);
push(S,p->;rchild); 
}
}
}



2)中序遍历

a)递归方式

void inorder_recursive(Bitree T)		/* 中序遍历二叉树的递归算法 */

{
if (T) {
inorder_recursive(T->;lchild); /* 访问左子树 */
visit(T);  /* 访问当前结点 */
inorder_recursive(T->;rchild); /* 访问右子树 */
}
}


b)非递归方式
void  inorder_nonrecursive(Bitree T)

{
initstack(S); /* 初始化栈 */
push(S, T); /* 根指针入栈 */

while (!stackempty(S)) {
while (gettop(S, p) && p)  /* 向左走到尽头 */
push(S, p->;lchild);
pop(S, p); /* 空指针退栈 */
if (!stackempty(S)) {
pop(S, p);
visit(p); /* 访问当前结点 */
push(S, p->;rchild); /* 向右走一步 */
}
}
}


3)后序遍历

a)递归方式
void postorder_recursive(Bitree T)		/* 中序遍历二叉树的递归算法 */

{
   if (T) {
   postorder_recursive(T->;lchild); /* 访问左子树 */
   postorder_recursive(T->;rchild); /* 访问右子树 */
   visit(T);  /* 访问当前结点 */
   }
}


b)非递归方式
typedef struct {

BTNode* ptr;
enum {0,1,2} mark;
} PMType;  /* 有mark域的结点指针类型 */

void postorder_nonrecursive(BiTree T) /* 后续遍历二叉树的非递归算法 */
{
PMType a;
initstack(S);  /* S的元素为PMType类型 */
push (S,{T,0});  /* 根结点入栈 */
while(!stackempty(S)) {
pop(S,a);
switch(a.mark)
{
case 0:
push(S,{a.ptr,1});  /* 修改mark域 */
if(a.ptr->;lchild) 
push(S,{a.ptr->;lchild,0}); /* 访问左子树 */
break;
case 1:
push(S,{a.ptr,2});  /* 修改mark域 */
if(a.ptr->;rchild) 
push(S,{a.ptr->;rchild,0}); /* 访问右子树 */
break;
case 2:
visit(a.ptr);  /* 访问结点 */
}
}
}

       4)如何实现递归与非递归的转换
          通常,一个函数在调用另一个函数之前,要作如下的事情:a)将实在参数,返回地址等信息传递
       给被调用函数保存; b)为被调用函数的局部变量分配存储区;c)将控制转移到被调函数的入口.
          从被调用函数返回调用函数之前,也要做三件事情:a)保存被调函数的计算结果;b)释放被调
       函数的数据区;c)依照被调函数保存的返回地址将控制转移到调用函数.
          所有的这些,不论是变量还是地址,本质上来说都是"数据",都是保存在系统所分配的栈中的.
  ok,到这里已经解决了第一个问题:递归调用时数据都是保存在栈中的,有多少个数据需要保存
       就要设置多少个栈,而且最重要的一点是:控制所有这些栈的栈顶指针都是相同的,否则无法实现
       同步.
          下面来解决第二个问题:在非递归中,程序如何知道到底要转移到哪个部分继续执行?回到上
       面说的树的三种遍历方式,抽象出来只有三种操作:访问当前结点,访问左子树,访问右子树.这三
       种操作的顺序不同,遍历方式也不同.如果我们再抽象一点,对这三种操作再进行一个概括,可以
       得到:a)访问当前结点:对目前的数据进行一些处理;b)访问左子树:变换当前的数据以进行下一次
       处理;c)访问右子树:再次变换当前的数据以进行下一次处理(与访问左子树所不同的方式).
          下面以先序遍历来说明:
void preorder_recursive(Bitree T)		/* 先序遍历二叉树的递归算法 */

{
if (T) {
visit(T);  /* 访问当前结点 */
preorder_recursive(T->;lchild); /* 访问左子树 */
preorder_recursive(T->;rchild); /* 访问右子树 */
}
}

   visit(T)这个操作就是对当前数据进行的处理, preorder_recursive(T->;lchild)就是把当前
数据变换为它的左子树,访问右子树的操作可以同样理解了.
   现在回到我们提出的第二个问题:如何确定转移到哪里继续执行?关键在于一下三个地方:a)
确定对当前数据的访问顺序,简单一点说就是确定这个递归程序可以转换为哪种方式遍历的树结
构;b)确定这个递归函数转换为递归调用树时的分支是如何划分的,即确定什么是这个递归调用
树的"左子树"和"右子树"c)确定这个递归调用树何时返回,即确定什么结点是这个递归调用树的
"叶子结点".

三.三个例子
   好了上面的理论知识已经足够了,下面让我们看看几个例子,结合例子加深我们对问题的认识
.即使上面的理论你没有完全明白,不要气馁,对事物的认识总是曲折的,多看多想你一定可以明
白(事实上我也是花了两个星期的时间才弄得比较明白得).
   
        1)例子一:
f(n) = n + 1;	(n <2)

 f[n/2] + f[n/4](n >;= 2);

这个例子相对简单一些,递归程序如下:
int f_recursive(int n)
{
int u1, u2, f;

if (n < 2) 
f = n + 1;
else {
u1 = f_recursive((int)(n/2));
u2 = f_recursive((int)(n/4));
f = u1 * u2;  
}

return f;
}


   下面按照我们上面说的,确定好递归调用树的结构,这一步是最重要的.首先,什么是叶子结点
,我们看到当n < 2时f = n + 1,这就是返回的语句,有人问为什么不是f = u1 * u2,这也是一个
返回的语句呀?答案是:这条语句是在u1 = exmp1((int)(n/2))和u2 = exmp1((int)(n/4))之后
执行的,是这两条语句的父结点. 其次,什么是当前结点,由上面的分析,f = u1 * u2即是父结点
.然后,顺理成章的u1 = exmp1((int)(n/2))和u2 = exmp1((int)(n/4))就分别是左子树和右子
树了.最后,我们可以看到,这个递归函数可以表示成后序遍历的二叉调用树.好了,树的情况分析
到这里,下面来分析一下栈的情况,看看我们要把什么数据保存在栈中,在上面给出的后序遍历的如果这个过程你没
非递归程序中我们已经看到了要加入一个标志域,因此在栈中要保存这个标志域;另外,u1,u2和
每次调用递归函数时的n/2和n/4参数都要保存,这样就要分别有三个栈分别保存:标志域,返回量
和参数,不过我们可以做一个优化,因为在向上一层返回的时候,参数已经没有用了,而返回量也
只有在向上返回时才用到,因此可以把这两个栈合为一个栈.如果对于上面的分析你没有明白,建
议你根据这个递归函数写出它的递归栈的变化情况以加深理解,再次重申一点:前期对树结构和
栈的分析是最重要的,如果你的程序出错,那么请返回到这一步来再次分析,最好把递归调用树和
栈的变化情况都画出来,并且结合一些简单的参数来人工分析你的算法到底出错在哪里.
    ok,下面给出我花了两天功夫想出来的非递归程序(再次提醒你不要气馁,大家都是这么过来
的).

int	f_nonrecursive(int n)

{
int stack[20], flag[20], cp;
 
/* 初始化栈和栈顶指针 */
cp = 0;
stack[0] = n;
flag[0] = 0;

while (cp >;= 0) {
switch(flag[cp]) {
case 0:  /* 访问的是根结点 */
if (stack[cp] >;= 2) { /* 左子树入栈 */
flag[cp] = 1;  /* 修改标志域 */
cp++;
stack[cp] = (int)(stack[cp - 1] / 2);
flag[cp] = 0;
} else {  /* 否则为叶子结点 */
stack[cp] += 1;
flag[cp] = 2;
}
break;
case 1:  /* 访问的是左子树 */
if (stack[cp] >;= 2) { /* 右子树入栈 */
flag[cp] = 2;  /* 修改标志域 */
cp += 2;
stack[cp] = (int)(stack[cp - 2] / 4);
flag[cp] = 1;
} else {  /* 否则为叶子结点 */
stack[cp] += 1;
flag[cp] = 2;
}
break;
case 2:  /* */
if (flag[cp - 1] == 2) { /* 当前是右子树吗? */
/* 
 * 如果是右子树, 那么对某一棵子树的后序遍历已经
 * 结束,接下来就是对这棵子树的根结点的访问
 */
stack[cp - 2] = stack[cp] * stack[cp - 1];
flag[cp - 2] = 2;
cp = cp - 2;
} else 
/* 否则退回到后序遍历的上一个结点 */
cp--;
break;
}
}

return stack[0];
}

           算法分析:a)flag只有三个可能值:0表示第一次访问该结点,1表示访问的是左子树,2表示
已经结束了对某一棵子树的访问,可能当前结点是这棵子树的右子树,也可能是叶子结点.b)每
遍历到某个结点的时候,如果这个结点满足叶子结点的条件,那么把它的flag域设为2;否则根据
访问的是根结点,左子树或是右子树来设置flag域,以便决定下一次访问该节点时的程序转向.


2)例子二

快速排序算法
递归算法如下:

void	swap(int array[], int low, int high)

{
int temp;

temp = array[low];
array[low] = array[high];
array[high] = temp;
}

int partition(int array[], int low, int high)
{
int p;

p = array[low];

while (low < high) {
while (low < high && array[high] >;= p) 
high--;
swap(array,low,high);
while (low < high && array[low]  <= p) 
low++;
swap(array,low,high);
}

return low;
}

void qsort_recursive(int array[], int low, int high)
{
int p;

if(low < high) {
p = partition(array, low, high);
qsort_recursive(array, low, p - 1);
qsort_recursive(array, p + 1, high);
}
}

   需要说明一下快速排序的算法: partition函数根据数组中的某一个数把数组划分为两个部分,
左边的部分均不大于这个数,右边的数均不小于这个数,然后再对左右两边的数组再进行划分.这
里我们专注于递归与非递归的转换,partition函数在非递归函数中同样的可以调用(其实
partition函数就是对当前结点的访问).
   再次进行递归调用树和栈的分析:
   递归调用树:a)对当前结点的访问是调用partition函数;b)左子树:
qsort_recursive(array, low, p - 1);c)右子树:qsort_recursive(array, p + 1, high);
d)叶子结点:当low < high时;e)可以看出这是一个先序调用的二叉树
   栈:要保存的数据是两个表示范围的坐标.
   
void	qsort_nonrecursive(int array[], int low, int high)

{
int m[50], n[50], cp, p; 

/* 初始化栈和栈顶指针 */
cp = 0;
m[0] = low;
n[0] = high;

while (m[cp] < n[cp]) {
while (m[cp] < n[cp]) { /* 向左走到尽头 */
p = partition(array, m[cp], n[cp]); /* 对当前结点的访问 */
cp++;
m[cp] = m[cp - 1];
n[cp] = p - 1;
}
/* 向右走一步 */
m[cp + 1] = n[cp] + 2;
n[cp + 1] = n[cp - 1];
cp++;
}
}


3)例子三
阿克曼函数:
akm(m, n) = n + 1;			(m = 0时)

    akm(m - 1, 1); (n = 0时)
    akm(m - 1, akm(m, n - 1)); (m != 0且n != 0时)
    

递归算法如下:

int	akm_recursive(int m, int n)

{
int temp;

if (m == 0) 
return (n + 1);
else if (n == 0) 
return akm_recursive(m - 1, 1);
else {
temp = akm_recursive(m, n - 1);
return akm_recursive(m - 1, temp);
}
}


这个例子相对难一些,不过只要正确的分析递归调用树和栈的变化情况就不难解决,先卖个关子,晚上再来公布答案,感兴趣的可以先想想.



 mirnshi 回复于:2004-05-25 00:15:35

引用:原帖由 "converse" 发表:

这个例子相对难一些,不过只要正确的分析递归调用树和栈的变化情况就不难解决,先卖个关子,晚上再来公布答案,感兴趣的可以先想想.



递归和非递归, 其实都是一样的.非递归需要人为构建维护堆栈.递归只是系统
在帮你维护堆栈而已. 数据结构上说得很清楚.


 五月 回复于:2004-05-25 11:27:42

即使不是递归设计,但你仍然在用递归的思想在写东西。:)
另外,尾递归的改写可以不用栈。


 converse 回复于:2004-05-25 15:32:18

好了,让我们回到递归与非递归的世界中,继续未完的旅途.

这道题的难点就是确定递归调用树的情况,因为从akm函数的公式可以看到,有三个递归调用,一般
而言,有几个递归调用就会有几棵递归调用的子树,不过这只是一般的情况,不一定准确,也不一定非要
机械化的这么作,因为通常情况下我们可以做一些优化,省去其中的一些部分,这道题就是一个例子.
    递归调用树的分析:a)是当m=0时是叶子结点;b)左子树是akm(m - 1, akm(m, n - 1))调用中的
akm(m, n - 1)调用,当这个调用结束得出一个值temp时,再调用akm(m - 1, temp),这个调用是右子树
.c)从上面的分析可以看出,这个递归调用树是后序遍历的树.
    栈的分析:要保存的数据是m, n,当n = 0 或 m = 0时开始退栈,当n = 0时把上一层栈的m值变为
m - 1,n变为1,当m = 0时把上一层栈的m值变为0,n变为n + 1.从这个分析过程可以看出,我们省略了
当n = 0时的akm(m - 1, 1)调用,原来在系统机械化的实现递归调用的过程中,这个调用也是一棵子树,
不过经过分析,我们用修改栈中数据的方式进行了改进.

int	akm_nonrecursive(int m, int n)

{
int m1[50], n1[50], cp;

cp = 0;
m1[0] = m;
n1[0] = n;

do {
while (m1[cp] >; 0) {  /* 压栈, 直到m1[cp] = 0 */
while (n1[cp] >; 0) {  /* 压栈, 直到n1[cp] = 0 */
cp++;
m1[cp] = m1[cp - 1];
n1[cp] = n1[cp - 1] - 1;
}
/* 计算akm(m - 1, 1),当n = 0时 */
m1[cp] = m1[cp] - 1;
n1[cp] = 1;
}
/* 改栈顶为akm(m - 1, n + 1),当m = 0时 */
cp--;
m1[cp] = m1[cp] - 1;
n1[cp] = n1[cp + 1] + 1;
} while (cp >; 0 || m1[cp] >; 0);

return n1[0] + 1;
}



 crazybaw 回复于:2004-05-25 15:42:24

最近看了本书,中文名字<C和指针>;(pointers on c),一外国人写的,在这本书中,对于递归等介绍了一些,也有一些堆栈调用的图,比较适合我们新人读啊.


 converse 回复于:2004-05-25 16:34:59

三.递归程序的分类及用途

   递归程序分为两类:尾部递归和非尾部递归.上面提到的几个例子都是非尾部递归,在一个选择分支中有至少
一个的递归调用.相对而言,尾部递归就容易很多了,因为与非尾部递归相比,每个选择分支只有一个递归调用,
我们在解决的时候就不需要使用到栈,只要循环和设置好循环体就可以了.下面再举几个尾部递归的例子吧,比较
简单我就不多说什么了.

   1)例子一
   
g(m, n) = 0			(m = 0, n >;= 0)

   = g(m - 1, 2n) + n; (m >; 0, n >;= 0)
   

   a)递归程序
int		g_recursive(int m, int n)

{
if (m == 0 && n >;= 0) 
return 0;
return (g_recurse(m - 1, 2*n) + n);
}

   b)非递归程序
int		g_nonrecursive(int m, int n)

{
int p;

for (p = 0; m >; 0 && n >;= 0; m--, n *= 2) 
p += n;

return p;
}

    2)例子二
   
 f(n) = n + 1 	(n = 0)

           n * f(n/2) (n >; 0)
   
    a)递归程序
int		f_recursive(int n)

{
if (n == 0) 
return 1;
return (n * f_recurse(n/2));
}

    b)非递归程序
int	f_nonrecursive(int n)

{
int m;

for (m = 1; n >; 0; n /= 2) 
m *= n;

return m++;
}

   
      分析完了递归程序的分类,让我们回头看看在向非递归转换的过程中用到了什么来实现转换:
   1)循环,因为程序要在某个条件下一直执行下去,要代替递归程序,循环必不可少,对于尾部递归,循环结束的
     条件十分容易确定,只要按照不同分支的条件写出来就可以了.而对于非尾部递归程序,循环结束的条件一
     般是当栈为空时或者是结束了对递归调用树的遍历从树的根结点退出时,而且有的时候写成while()的形式
     ,有时写成do ...while的形式(如上面的akm函数),具体怎样,很难说清楚,取决于你对整个递归程序的分析
     .
   2)递归调用树,树的结构在转换的过程中是不可见的,你不必为转换专门写一个树结构,不过能不能把递归调用
     中的树遍历方式以及叶子结点,左子树,右子树等元素确定好是你能否正确解决问题的关键(这一点已经在
     上面的分析过程中展露无疑),确定好这些后,剩下的工作大部分就是按照给出的几种不同的遍历树的方式
     把程序进行改写,这个过程就考验你对树结构还有遍历方式是否很好的掌握了(看出基础的重要了吗?如果
     回答是,那么和我一样好好的打好基础吧,一切都还不晚!!).对于尾部递归而言,可以看作没有递归调用树,
     所以尾部递归的难度大大降低了.
   3)栈,非尾部调用中需要栈来保存数据,这一点已经很清楚了,需要注意几个问题:a)栈有时可能会出现不够的
     情况,拿上面的akm函数来说,我用的50个元素的数组,你如果把m和n值设置得大一些,这个栈就不能用了,有
     时你的算法正确了,不过没有注意到这个问题还是会出错的;反过来说,在递归调用中,系统或者编译器的优
     化功能不够好的化,在这个栈上可能会消耗很多空间,这个时候如果你把程序改成非递归的形式,然后再用
     动态分配技术分配栈可能就会把程序的性能提高一大块--这也是我们学习这门技术的意义之一,因为系统
     是机械化的,你如果知道更好的优化办法,为什么不用呢?
     
   什么时候可以用递归解决问题?到了这一步,如果你对于上面说的已经相当明白的话,这个问题不难回答,如果
   我们要解决的问题要分成几个小的部分,而其中的一些与你要解决的问题是一样的,只不过是问题的规模(如
   参数等)小了,那么这样的问题可以用递归来解决.根据问题设计好一个递归是所有这些的基础,转换也是在原
   来的递归程序上进行的,所以这一步一定要做好.通常,设计一个递归程序要注意一下几个问题:a)可以递归解
   决的问题是什么?b)入口和出口参数是什么--即要明确好出入的接口.
   
四.学习过程中参考到的一些资料

   1)<<算法导论>;>;  国防科大出版社  张益新 沈雁编著
     这本书比较老了,也许很难找了,不过参考价值不大,里面专门用了一章来讲这个问题,可惜用的是goto来实
     现递归调用的选择分支(所以我说参考价值不大),如果没有也无所谓的.
     
   2)http://db.pku.edu.cn/mzhang/  北京大学张铭老师的主页.
     上面可以找到一个张老师讲解递归和非递归转换的视频,有大概50多分钟吧,我没有听完,原先看第一本书
     的对于如何不用goto实现选择分支十分不解,张老师讲到的递归调用树真正让我豁然开朗,从此以后虽然还
     是有很多问题,不过明白了这个大体的解决思路就有了.
     
   3)<<数据结构>;>;  清华大学出版社  严蔚敏著
     这本书讲到栈的时候略微讲到了一些这方面的内容,网上可以找到一位叫一具的仁兄写的这本书的习题解答
     ,可以自己去找一找,我的中序和后序遍历的算法完全看的他写的.
    
   4) http://www.tztvu.edu.cn/kfjy/bkjy/jsj/bxk/sjjg/kcdh/忘了是什么的网页了
     里面的一个dn5.doc的文件中有讲解转换akm函数的方法,给我的启发很大,如果你对于我说的akm函数还有疑
     问,那么可以看看人家写的,他把递归调用树和栈的变化情况都写出来了,非常直观.


 

原文链接:http://bbs.chinaunix.net/viewthread.php?tid=331522
转载请注明作者名及原文出处

posted @ 2006-06-27 15:27 星空下的悠云 阅读(791) 评论(0) 编辑

ArrayList最佳使用建议

ArrayList最佳使用建议
    这一节我们来讨论ArrayList与数组的差别,以及ArrayList的效率问题
  1)ArrayList是Array的复杂版本
ArrayList内部封装了一个Object类型的数组,从一般的意义来说,它和数组没有本质的差别,甚至于ArrayList的许多方法,如Index、IndexOf、Contains、Sort等都是在内部数组的基础上直接调用Array的对应方法。
  2)内部的Object类型的影响
         对于一般的引用类型来说,这部分的影响不是很大,但是对于值类型来说,往ArrayList里面添加和修改元素,都会引起装箱和拆箱的操作,频繁的操作可能会影响一部分效率。
但是恰恰对于大多数人,多数的应用都是使用值类型的数组。
消除这个影响是没有办法的,除非你不用它,否则就要承担一部分的效率损失,不过这部分的损失不会很大。
  3)数组扩容
这是对ArrayList效率影响比较大的一个因素。
每当执行Add、AddRange、Insert、InsertRange等添加元素的方法,都会检查内部数组的容量是否不够了,如果是,它就会以当前容量的两倍来重新构建一个数组,将旧元素Copy到新数组中,然后丢弃旧数组,在这个临界点的扩容操作,应该来说是比较影响效率的。
     例1:比如,一个可能有200个元素的数据动态添加到一个以默认16个元素大小创建的ArrayList中,将会经过:
16*2*2*2*2 = 256
四次的扩容才会满足最终的要求,那么如果一开始就以:
ArrayList List = new ArrayList( 210 );
的方式创建ArrayList,不仅会减少4次数组创建和Copy的操作,还会减少内存使用。

     例2:预计有30个元素而创建了一个ArrayList:
ArrayList List = new ArrayList(30);
在执行过程中,加入了31个元素,那么数组会扩充到60个元素的大小,而这时候不会有新的元素再增加进来,而且有没有调用TrimSize方法,那么就有1次扩容的操作,并且浪费了29个元素大小的空间。如果这时候,用:
ArrayList List = new ArrayList(40);
那么一切都解决了。
所以说,正确的预估可能的元素,并且在适当的时候调用TrimSize方法是提高ArrayList使用效率的重要途径。
   4)频繁的调用IndexOf、Contains等方法(Sort、BinarySearch等方法经过优化,不在此列)引起的效率损失
首先,我们要明确一点,ArrayList是动态数组,它不包括通过Key或者Value快速访问的算法,所以实际上调用IndexOf、Contains等方法是执行的简单的循环来查找元素,所以频繁的调用此类方法并不比你自己写循环并且稍作优化来的快,如果有这方面的要求,建议使用Hashtable或SortedList等键值对的集合。
ArrayList al=new ArrayList();

al.Add("How");
al.Add("are");
al.Add("you!");

al.Add(100);
al.Add(200);
al.Add(300);

al.Add(1.2);
al.Add(22.8);

.........

//第一种遍历 ArrayList 对象的方法
foreach(object o in al)
{
Console.Write(o.ToString()+" ");
}

//第二种遍历 ArrayList 对象的方法
IEnumerator ie=al.GetEnumerator();
while(ie.MoveNext())
{
Console.Write(ie.Curret.ToString()+" ");
}

//第三种遍历 ArrayList 对象的方法
我忘记了,好象是 利用 ArrayList对象的一个属性,它返回一此对象中的元素个数.

然后在利用索引 
for(int i=0;i<Count;i++)
{
Console.Write(al[i].ToString()+" ");
}

posted @ 2006-06-27 13:46 星空下的悠云 阅读(194) 评论(2) 编辑

2006年6月26日 #

浅析.NET中的Serialization

Serialization的概念


Serialization是.NET中一种实现对象持久性(Persistent)的机制。它是一个将对象中的数据转换成一个单一元素(通常是Stream)的过程。它的逆过程是Deserialization。Serialization的核心概念是将一个对象的所有数据看作一个独立的单元。

一般说来,在两种情况下非常需要Serialization:1)当我们希望能够将对象当前的状态完整地保存到存储介质中,以便我们以后能够精确地还原对象时;2)当我们希望将对象从一个应用程序空间(Application domain)传递到另一个应用程序空间时。例如,Windows Form程序就是利用Serialization机制来实现剪贴板的copy & paste的。

.NET Framework支持两种类型的Serialization:Shallow Serialization和Deep Serialization。

所谓Shallow Serialization是将对象的可读写(read-write)属性的值转换成字节流,而对象内部的数据(没有通过read-write属性暴露出来的数据)则不被转换。XmlSerializer以及Web Services就使用这种技术。

Deep Serialization比Shallow Serialization更加彻底,因为它是将存储在对象私有变量里的实际值拷贝到字节流里。而且Deep Serialization还将serialize整个object graph。也就是说,如果你的对象持有其他对象的引用,或者其他对象引用的集合,那么所有这些对象都将被Serialize。BinaryFormatter和SoapFormatter以及.NET Remoting都使用Deep Serialization技术,它甚至被有限地用于LosFormatter来产生存储在Web Form页中的状态数据。

本文将着重于Deep Serialization。


Serialization的过程


.NET Framework通过Reflection提供自动Serialization的机制。当一个对象被序列化(Serialized)的时候,它的类名,Assembly,以及类实例的所有数据成员都将被写入存储介质中。Serialization引擎保持对所有已经被序列化的对象引用的追踪,以确保相同的对象引用最多只被序列化一次。

通常,一个Serialization过程会由formatter(例如BinaryFormatter)的Serialize方法引发。对象的Serialization过程按照以下规则进行:

1、 检测以确保formatter是否拥有一个代理选择器(surrogate selector)。如果有,检查代理选择器是否持有给定的对象类型。如果有,ISerializable.GetObjectData被调用。

2、 如果formatter没有代理选择器,或者代理选择器没有对象类型,检查对象是否被用Serializable属性标记。如果没有,则抛出SerializationException异常。

3、 如果对象被标记为Serializable,检查对象是否实现了ISerializable接口。如果实现了此接口,则GetObjectData被调用。

4、 如果对象没有实现ISerializable接口,则使用默认的序列化策略,来序列化没有用NonSerialized属性标记的域。

使你的class能够被序列化

通过上面对Serialization过程的分析,我们可以看出,有两种方式可以使一个class能够被序列化:1)将此class简单地标记为Serializable;2)为此class实现ISerializable接口,并将此class标记为Serializable。

1、 标记Serializable属性

标记Serializable属性的方式是实现Serialization的基本方法。举个简单的例子:


[Serializable]

public class Person

{

public string name = null;

public int age = 0;

}


你可以使用BinaryFormatter来将上面的class序列化:

Person sam = new Person();

sam.name = "sam";

sam.age = 24;

IFormatter formatter = new BinaryFormatter();

Stream stream = new FileStream("sam.dat",

FileMode.Create, FileAccess.Write, FileShare.None);

formatter.Serialize(stream, sam);

stream.Close();


就是这么简单,你所要做的就是创建一个Stream和一个formatter的实例,然后调用formatter的Serialize方法。经过BinaryFormatter serialize的数据仍然能够通过BinaryFormatter deserialize回来,方法与serialize同样简单,这里就不赘述了。

如果你不想将类里的所有域都序列化,可以使用NonSerialized属性进行选择。如:

[Serializable]

public class Person

{

public string name = null;

[NonSerialized]

public int age = 0;

}


这样,age域就不会被序列化了。

需要注意的是,Serializable属性并不能被继承。也就是说如果你希望Person的派生类也能够被Serialize的话,那么这个派生类也必须被Serializable标记。否则将得到SerializationException异常。

同样的,Person类中的所有对其他类的引用,其所引用的类都应该是能够被Serialize的。.NET Framework中的大部分class都实现了ISerializable接口,但有些class没有实现,例如ImageList。可以通过MSDN Library的到一个实现了ISerializable接口的class列表。对那些没有实现此接口的class,使用的时候要当心。

2、 实现ISerializable接口

Serializable属性的功能非常强大,它使得Serialize和Deserialize变得十分简单。但凡事有利必有弊,由Serializable实现的自动序列化方法有时不够灵活。我们并不能完全控制Serialize和Deserialize的行为,而有些时候它们的行为对我们来说很重要。那么我们通过何种方法能够控制Serialize和Deserialize的行为呢?答案就是,自己来实现ISerializable接口。ISerializable接口给予我们更大的自由来控制Serialize和Deserialize,但是无疑我们将不得不写更多的代码L。

下面我们来看看如何实现ISerializabe接口。ISerializable接口位于System.Runtime.Serialization名字空间中,声明如下:

public inferface ISerializable

{

void GetObjectData(SerializationInfo info,

StreamingContext context);

}

它只有一个方法GetObjectData。因此,像实现其他接口一样,我们必须实现此方法。但与其他接口不同的是,为了Deserialization,我们还必须实现一个特殊的构造函数(我称此构造函数为“序列化构造函数”),此构造函数具有与GetObjectData相同的参数列表。由于此构造函数专门用于.NET Framework在Deserialize时的Reflection机制,因此我们通常将它声明为保护或私有模式。如下:(当然,如果你的class只需要Serialize而不需要Deserialize的话,也可以不实现这个特殊的构造函数)

[Serializable]

public class Person : ISerializable

{

public string name = null;

public int age = 0;


public Person()

{

}


protected Person(SerializationInfo info, StreamingContext context)

{

name = info.GetString("name");

age = info.GetInt32("age");

}


void ISerializable.GetObjectData(SerializationInfo info,

StreamingContext context)

{

info.AddValue("name", name);

info.AddValue("age", age);

}

}


通过实现ISerializable接口,使得我们有机会在ISerializable.GetObjectData中控制Serialize的行为,在“序列化构造函数”中控制Deserialize的行为。这个接口提供给我们的信息非常全面而灵活,以致于我们甚至可以在这两个方法中耍些花招。比如,我们可以在Deserialize的时候,籍由改变info.FullTypeName来得到一种与被Serialize的对象不同类型的另一个对象等。

独辟蹊径


前面谈到过Serialization被运用的典型环境,是对象存储、进程间数据传递等涉及到对象持久性的领域。但实际上,它也能够被运用到其他的许多地方,关键在于我们是否能想到去用运Serialization,有时候思维定式也是很可怕的J。举个例子,我们来看看在Clone方法中如何使用Serialization[1]。

如果我们要为Person类实现Clone方法,我们通常会这样写:

[Serializable]

public class Person : ICloneable

{

public string name = null;

public int age = 0;


public object Clone()

{

Person person = new Person();

person.name = name;

person.age = age;


return person;

}

}


如果我们利用Serialization的方法,Clone函数就能写成下面的样子:

public object Clone()

{

MemoryStream stream = new MemoryStream();

BinaryFormatter formatter = new BinaryFormatter();


formatter.Serialize(stream, this);

stream.Position = 0;

return formatter.Deserialize(stream);

}

从这两个实现上看,使用Serialization实现Clone方法似乎并没有什么好处。可是设想如果你面对的是一个复杂的类继承体系,从基类到派生类都需要实现Clone方法。利用第一种实作手法,你将不得不为每一个class写一个Clone方法,而且随着数据成员的增多,这个方法将越来越冗长,并且会由于数据成员的改变而引发错误(我曾经遇到过好几次,由于class中增加了成员变量,而Clone方法没有及时更新,导致运行时错误。呵,这种错误还很难调试)。现在你看到用Serialization实现的好处了吧?是的,我们只要在基类中将Clone方法声明为virtual,并用Serialization的方法实现之,然后保证基类和派生类都可以被Serialize,上面所有的麻烦不都迎刃而解了吗?


总结

现代软件项目中,无论何种项目都会或多或少地涉及到对象持久性的问题,.NET也不例外,无论是Windows Form、ASP.NET,还是Web Services,都需要处理对象持久性。而Serialization正是.NET为应对这个问题而给出的解法。

参考文献

·[1] Rockford Lhotka,《Object Serialization in Visual Basic .NET》,MSDN Library。Serialization在Clone方法中的运用即来自此文。

·Piet Obermeyer and Jonathan Hawkins,《Object Serialization in the .NET Framework》,MSDN Library。

·Jeffrey Richter,《.NET Run-time Serialization》Part 1,Part 2,Part 3,MSDN Library。

posted @ 2006-06-26 13:29 星空下的悠云 阅读(232) 评论(2) 编辑

2006年6月9日 #

持久性保存技术的一点心得(C#)

posted @ 2006-06-09 17:56 星空下的悠云 阅读(173) 评论(0) 编辑

2006年6月2日 #

Log4Net使用指南

posted @ 2006-06-02 14:21 星空下的悠云 阅读(2398) 评论(1) 编辑

2006年5月31日 #

C#中的非安全编程

posted @ 2006-05-31 18:10 星空下的悠云 阅读(139) 评论(0) 编辑

仅列出标题  下一页