BinaryTree with C#

上了三年半的大学,学的计算机,我却一直不知道二叉树是干什么使的。最近在看《Visual C# 2005 从入门到精通》,书中说.NET类库里缺少二叉树这个类,于是就想自己写一个,没想到一边写着一边思考,竟也把二叉树的用处以及其优势劣势搞明白了。

二叉树有什么用?
二叉树本身没有什么用,其主要作用在于有序二叉树(下文简称“有序二叉树”为“二叉树”)。二叉树在插入和查找的时候具有高效率。为什么会具有高效率?因为二叉树保持总保持一个有序状态:即左子树小于根节点,根节点小于右子树。这样的结构使得二叉树在查找上具有与二分搜索同样的效率。当然,排好序的的一维数组也可以进行二分搜索。那二叉树的优势在哪呢?就在于其插入的效率比数组高,二叉树不用为了给新添加的元素腾出地方而移动大量元素。二叉树插入的效率与搜索的效率是相等的。

对比如下:(lgN是以2为底N的对数)
二叉树:搜索效率=插入效率=lgN
有序数组:搜索效率=lgN,插入效率=lgN + N/2

由此不难得到二叉树的适用场合:需要经常插入和删除元素且对速度要求较高的场合。
比如要举办一个大型网络会议,有成千上万的人参加,并且随着时间的推移会不断有新的人加入进来或离开。这种场合就很适合用二叉树。

有序性是二叉树最重要特点,编程的时候决不应该破坏有序性。为了保持有序性,在删除节点的时候就会遇到一些麻烦。以前在《数据结构》课本上见过删除节点的算法,很复杂而且很难读懂。于是我就干脆自己写一个删除节点的算法,费了好一番周折。开始我打算从物理上删除,即把要删除的节点真的从树中拿掉。这种做法带来的问题就是,被删节点的左右两棵子树不知该如何安放。为了解决这个问题,我在类中添加了SearchParent方法,甚至还想添加一个parent字段以提高效率,后来越做越觉得麻烦。最后我想出一个好的解决办法,给每个节点添加一个removed字段,用来标识节点是否已被删除。这样之前为了删除节点而写的那一堆辅助方法就都用不着了!可见优秀的设计比优秀的算法更有效!有了removed字段,删除节点的效率就与插入和搜索的效率一样,都是lgN。

对二叉树来说,有序性第一重要,位列第二的就是平衡性了。一个极不平衡的二叉树的效率是很低的。为了保持高效率,我添加了Optimize方法来使二叉树变得平衡。只有在平衡结构下,效率才能达到上面所说的lgN。《数据结构》课本上也介绍过平衡二叉树的算法,但相当繁琐。我自己写了一个,可能效率不是最高,但算法简单易理解。

我写的这个二叉树类有很多方法用到了递归。虽然递归在速度上不是很快,但能大大简化算法,避免了很多因算法设计不当而引入的bug。我信奉的编程思想是:首先保证简单,然后才追求速度。

我目前还是学生,编程肯定不够专业,在此将源代码奉上,望您提出宝贵意见!新年到了,愿2007是您丰收的一年!


 

None.gif using  System;
None.gif
using  System.Collections.Generic;
None.gif
using  System.Text;
None.gif
None.gif
None.gif
namespace  OrderedBinaryTree
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
ExpandedSubBlockStart.gifContractedSubBlock.gif    
/**//// <summary>
InBlock.gif    
/// 有序二叉树
InBlock.gif    
/// 作者: dc10101
InBlock.gif    
/// 完成日期:2007/1/1
InBlock.gif    
/// </summary>
ExpandedSubBlockEnd.gif    
/// <typeparam name="T"></typeparam>

InBlock.gif    public class Tree<T> where T:IComparable<T>
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.gif{
ContractedSubBlock.gifExpandedSubBlockStart.gif        
fields#region fields
InBlock.gif
InBlock.gif        
private T data; // 节点的值
InBlock.gif
        private Tree<T> left;   // 左子树
InBlock.gif
        private Tree<T> right;  // 右子树
InBlock.gif
        private bool removed = false;   // 标记该节点是否已被删除。true:已被删除;false: 未被删除
InBlock.gif
        
ExpandedSubBlockEnd.gif        
#endregion

InBlock.gif
ContractedSubBlock.gifExpandedSubBlockStart.gif        
properties#region properties
InBlock.gif
ExpandedSubBlockStart.gifContractedSubBlock.gif        
/**//// <summary>
InBlock.gif        
/// 当前节点的值
InBlock.gif        
/// readonly,随意更改节点的值将有可能破坏二叉树的有序结构
ExpandedSubBlockEnd.gif        
/// </summary>

InBlock.gif        public T Data
ExpandedSubBlockStart.gifContractedSubBlock.gif        
dot.gif{
ExpandedSubBlockStart.gifContractedSubBlock.gif            
get dot.gifreturn this.data; }            
ExpandedSubBlockEnd.gif        }
// Data
InBlock.gif

ExpandedSubBlockStart.gifContractedSubBlock.gif        
/**//// <summary>
InBlock.gif        
/// 以当前节点为根的树中的节点总数
InBlock.gif        
/// 不包括已被标记为deleted的节点
InBlock.gif        
/// readonly,此属性是一个纯粹的方法,根本就不存在相应的字段,因此无法set
InBlock.gif        
/// 递归方法
ExpandedSubBlockEnd.gif        
/// </summary>

InBlock.gif        public int NodeCount
ExpandedSubBlockStart.gifContractedSubBlock.gif        
dot.gif{
InBlock.gif            
get 
ExpandedSubBlockStart.gifContractedSubBlock.gif            
dot.gif{
InBlock.gif                
int leftCount = 0;
InBlock.gif                
int rightCount = 0;
InBlock.gif                
if (this.left != null)
ExpandedSubBlockStart.gifContractedSubBlock.gif                
dot.gif{
InBlock.gif                    leftCount 
= this.left.NodeCount;
ExpandedSubBlockEnd.gif                }

InBlock.gif                
if (this.right != null)
ExpandedSubBlockStart.gifContractedSubBlock.gif                
dot.gif{
InBlock.gif                    rightCount 
= this.right.NodeCount;
ExpandedSubBlockEnd.gif                }

InBlock.gif
InBlock.gif                
// 左右子树都为null,递归出口
InBlock.gif
                if (this.removed)
ExpandedSubBlockStart.gifContractedSubBlock.gif                
dot.gif{
InBlock.gif                    
return leftCount + rightCount; 
ExpandedSubBlockEnd.gif                }

InBlock.gif                
else
ExpandedSubBlockStart.gifContractedSubBlock.gif                
dot.gif{
InBlock.gif                    
return leftCount + rightCount + 1
ExpandedSubBlockEnd.gif                }
               
ExpandedSubBlockEnd.gif            }
            
ExpandedSubBlockEnd.gif        }
// NodeCount
InBlock.gif

ExpandedSubBlockStart.gifContractedSubBlock.gif        
/**//// <summary>
InBlock.gif        
/// 以当前节点为根的二叉树的层数
InBlock.gif        
/// readonly
InBlock.gif        
/// 递归方法
ExpandedSubBlockEnd.gif        
/// </summary>

InBlock.gif        public int LevelCount 
ExpandedSubBlockStart.gifContractedSubBlock.gif        
dot.gif{
InBlock.gif            
get 
ExpandedSubBlockStart.gifContractedSubBlock.gif            
dot.gif{                                
InBlock.gif                
int leftLevelCount;
InBlock.gif                
int rightLevelCount;
InBlock.gif                
if (this.left != null)
ExpandedSubBlockStart.gifContractedSubBlock.gif                
dot.gif{
InBlock.gif                    leftLevelCount 
= this.left.LevelCount;
ExpandedSubBlockEnd.gif                }

InBlock.gif                
else
ExpandedSubBlockStart.gifContractedSubBlock.gif                
dot.gif{
InBlock.gif                    leftLevelCount 
= 0;
ExpandedSubBlockEnd.gif                }

InBlock.gif
InBlock.gif                
if (this.right != null)
ExpandedSubBlockStart.gifContractedSubBlock.gif                
dot.gif{
InBlock.gif                    rightLevelCount 
= this.right.LevelCount;
ExpandedSubBlockEnd.gif                }

InBlock.gif                
else
ExpandedSubBlockStart.gifContractedSubBlock.gif                
dot.gif{
InBlock.gif                    rightLevelCount 
= 0;
ExpandedSubBlockEnd.gif                }

InBlock.gif
InBlock.gif                
int max = (leftLevelCount > rightLevelCount) ? leftLevelCount : rightLevelCount;
InBlock.gif                
return max + 1;
ExpandedSubBlockEnd.gif            }

ExpandedSubBlockEnd.gif        }
// LevelCount
InBlock.gif

ExpandedSubBlockStart.gifContractedSubBlock.gif        
/**//// <summary>
InBlock.gif        
/// 将此数组中的元素从头到尾依次插入到二叉树中,会得到最佳结构的平衡二叉树
InBlock.gif        
/// readonly,此属性是一个纯粹的方法,根本就不存在相应的字段,因此无法set
ExpandedSubBlockEnd.gif        
/// </summary>

InBlock.gif        public T[] OptimizedArray
ExpandedSubBlockStart.gifContractedSubBlock.gif        
dot.gif{
InBlock.gif            
get
ExpandedSubBlockStart.gifContractedSubBlock.gif            
dot.gif{
InBlock.gif                T[] sortedArray 
= this.SortedArray;
InBlock.gif                
int arrayLength = this.NodeCount;
InBlock.gif                T[] newArray 
= new T[arrayLength];
InBlock.gif
InBlock.gif                
int optIndex = 0;
InBlock.gif                FillOptimizedArray(sortedArray, 
0, arrayLength - 1, newArray, ref optIndex);//FillOptimizedArray是递归函数
InBlock.gif
                return newArray;
ExpandedSubBlockEnd.gif            }

ExpandedSubBlockEnd.gif        }
// OptimizedArray
InBlock.gif

ExpandedSubBlockStart.gifContractedSubBlock.gif        
/**//// <summary>
InBlock.gif        
/// 中序遍历二叉树而得到的有序数组
InBlock.gif        
/// readonly,此属性是一个纯粹的方法,根本就不存在相应的字段,因此无法set
ExpandedSubBlockEnd.gif        
/// </summary>

InBlock.gif        public T[] SortedArray
ExpandedSubBlockStart.gifContractedSubBlock.gif        
dot.gif{
InBlock.gif            
get 
ExpandedSubBlockStart.gifContractedSubBlock.gif            
dot.gif{
InBlock.gif                T[] orderedArray 
= new T[this.NodeCount];
InBlock.gif                
int index = 0;
InBlock.gif                FillSortedArray(orderedArray, 
ref index);// FillSortedArray是个递归函数
InBlock.gif
                return orderedArray;
ExpandedSubBlockEnd.gif            }

ExpandedSubBlockEnd.gif        }
// SortedArray
InBlock.gif
        
ExpandedSubBlockEnd.gif        
#endregion

InBlock.gif
ContractedSubBlock.gifExpandedSubBlockStart.gif        
methods#region methods
InBlock.gif
InBlock.gif
ExpandedSubBlockStart.gifContractedSubBlock.gif        
/**//// <summary>
InBlock.gif        
/// 将指定节点插入到树中
InBlock.gif        
/// 与当前节点相比,小于插到左子树,大于插到右子树,相等不作任何操作
InBlock.gif        
/// 递归方法
InBlock.gif        
/// </summary>
ExpandedSubBlockEnd.gif        
/// <param name="newData">要插入的元素的值</param>

InBlock.gif        public void Add(T newData)
ExpandedSubBlockStart.gifContractedSubBlock.gif        
dot.gif{
InBlock.gif            
if (this.data.CompareTo(newData) > 0// currentData > newData,放到左子树上
ExpandedSubBlockStart.gifContractedSubBlock.gif
            dot.gif{
InBlock.gif                
if (null == this.left)// 递归出口1/2
ExpandedSubBlockStart.gifContractedSubBlock.gif
                dot.gif{
InBlock.gif                    
this.left = new Tree<T>(newData);
ExpandedSubBlockEnd.gif                }

InBlock.gif                
else
ExpandedSubBlockStart.gifContractedSubBlock.gif                
dot.gif{
InBlock.gif                    
this.left.Add(newData);
ExpandedSubBlockEnd.gif                }

ExpandedSubBlockEnd.gif            }

InBlock.gif
InBlock.gif            
if (this.data.CompareTo(newData) < 0// // currentData < newData,放到右子树上
ExpandedSubBlockStart.gifContractedSubBlock.gif
            dot.gif{
InBlock.gif                
if (null == this.right)// 递归出口2/2
ExpandedSubBlockStart.gifContractedSubBlock.gif
                dot.gif{
InBlock.gif                    
this.right = new Tree<T>(newData);
ExpandedSubBlockEnd.gif                }

InBlock.gif                
else
ExpandedSubBlockStart.gifContractedSubBlock.gif                
dot.gif{
InBlock.gif                    
this.right.Add(newData);
ExpandedSubBlockEnd.gif                }

ExpandedSubBlockEnd.gif            }

ExpandedSubBlockEnd.gif        }
// Add
InBlock.gif

ExpandedSubBlockStart.gifContractedSubBlock.gif        
/**//// <summary>
InBlock.gif        
/// 将sortedArray里正中间的元素添加到optimizedArray的尾部
InBlock.gif        
/// 递归重复此过程,最终得到数组optimizedArray,此数组最适合构造具有高效结构的平衡二叉树
InBlock.gif        
/// 内部私有方法,是属性OptimizedArray调用的子过程
InBlock.gif        
/// 递归方法
InBlock.gif        
/// </summary>
InBlock.gif        
/// <param name="sortedArray">按由小到大排好序的数组</param>
InBlock.gif        
/// <param name="begin">已经在sortedArray中锁定了一个范围,这个范围的起始位置</param>
InBlock.gif        
/// <param name="end">已经在sortedArray中锁定了一个范围,这个范围的末尾位置</param>
InBlock.gif        
/// <param name="optimizedArray">要填充的“优化数组”</param>
ExpandedSubBlockEnd.gif        
/// <param name="optIndex">“优化数组”中下一个要被填充的位置</param>

InBlock.gif        private void FillOptimizedArray(T[] sortedArray, int begin, int end, T[] optimizedArray, ref int optIndex)
ExpandedSubBlockStart.gifContractedSubBlock.gif        
dot.gif{            
InBlock.gif            
if (end - begin < 2)// 递归出口
ExpandedSubBlockStart.gifContractedSubBlock.gif
            dot.gif{
InBlock.gif                
if (end == begin)
ExpandedSubBlockStart.gifContractedSubBlock.gif                
dot.gif{
InBlock.gif                    optimizedArray[optIndex
++= sortedArray[begin];
ExpandedSubBlockEnd.gif                }

InBlock.gif                
else
ExpandedSubBlockStart.gifContractedSubBlock.gif                
dot.gif{
InBlock.gif                    optimizedArray[optIndex
++= sortedArray[begin];
InBlock.gif                    optimizedArray[optIndex
++= sortedArray[end];
ExpandedSubBlockEnd.gif                }

ExpandedSubBlockEnd.gif            }

InBlock.gif            
else
ExpandedSubBlockStart.gifContractedSubBlock.gif            
dot.gif{
InBlock.gif                
int middle = (begin + end) / 2;
InBlock.gif                optimizedArray[optIndex
++= sortedArray[middle];
InBlock.gif                FillOptimizedArray(sortedArray, begin, middle 
- 1, optimizedArray, ref optIndex);
InBlock.gif                FillOptimizedArray(sortedArray, middle 
+ 1, end, optimizedArray, ref optIndex);
ExpandedSubBlockEnd.gif            }

ExpandedSubBlockEnd.gif        }
// FillOptimizedArray
InBlock.gif

ExpandedSubBlockStart.gifContractedSubBlock.gif        
/**//// <summary>
InBlock.gif        
/// 从数组的指定下标起往后面添加有序序列
InBlock.gif        
/// 内部私有方法,是属性SortedArray调用的子过程
InBlock.gif        
/// 递归方法
InBlock.gif        
/// </summary>
InBlock.gif        
/// <param name="array">需要填充的数组</param>
ExpandedSubBlockEnd.gif        
/// <param name="index">所添加序列在数组中的起始下标</param>

InBlock.gif        private void FillSortedArray(T[] array, ref int index)
ExpandedSubBlockStart.gifContractedSubBlock.gif        
dot.gif{
InBlock.gif            
if (null != this.left)
ExpandedSubBlockStart.gifContractedSubBlock.gif            
dot.gif{
InBlock.gif                
this.left.FillSortedArray(array, ref index);
ExpandedSubBlockEnd.gif            }

InBlock.gif
InBlock.gif            
if (false == this.removed)// 递归出口
ExpandedSubBlockStart.gifContractedSubBlock.gif
            dot.gif{
InBlock.gif                array[index
++= this.data;
ExpandedSubBlockEnd.gif            }

InBlock.gif
InBlock.gif            
if (null != this.right)
ExpandedSubBlockStart.gifContractedSubBlock.gif            
dot.gif{
InBlock.gif                
this.right.FillSortedArray(array, ref index);
ExpandedSubBlockEnd.gif            }

ExpandedSubBlockEnd.gif        }
// FillSortedArray        
InBlock.gif

ExpandedSubBlockStart.gifContractedSubBlock.gif        
/**//// <summary>
InBlock.gif        
/// 优化以当前节点为根节点的二叉树,使其成为具有高效结构的平衡二叉树
ExpandedSubBlockEnd.gif        
/// </summary>

InBlock.gif        public void Optimize()
ExpandedSubBlockStart.gifContractedSubBlock.gif        
dot.gif{
InBlock.gif            T[] tempArray 
= this.OptimizedArray;
InBlock.gif            
// 如果this不是只读的,我才不会写下面这三行别扭的代码!!
InBlock.gif
            this.data = tempArray[0];//根节点必然是首先要插入的节点,即OptimizedArray数组的首元素
InBlock.gif
            this.left = null;
InBlock.gif            
this.right = null;
InBlock.gif
InBlock.gif            
foreach (T element in tempArray)// 依次插入每个节点
ExpandedSubBlockStart.gifContractedSubBlock.gif
            dot.gif{
InBlock.gif                
this.Add(element);
ExpandedSubBlockEnd.gif            }

ExpandedSubBlockEnd.gif        }
// Optimize
InBlock.gif

ExpandedSubBlockStart.gifContractedSubBlock.gif        
/**//// <summary>
InBlock.gif        
/// 删除指定的节点
InBlock.gif        
/// 并非把该节点从树中拿掉,而是将其removed字段标记为true
InBlock.gif        
/// 找到则删除,找不到则什么都不做
InBlock.gif        
/// </summary>
ExpandedSubBlockEnd.gif        
/// <param name="removeMe">指定节点的data值</param>

InBlock.gif        public void Remove(T removeMe)
ExpandedSubBlockStart.gifContractedSubBlock.gif        
dot.gif{
InBlock.gif            Tree
<T> deleteMe = this.Search(removeMe);
InBlock.gif            
if (deleteMe != null)
ExpandedSubBlockStart.gifContractedSubBlock.gif            
dot.gif{
InBlock.gif                deleteMe.removed 
= true;
ExpandedSubBlockEnd.gif            }

ExpandedSubBlockEnd.gif        }
// Remove
InBlock.gif

ExpandedSubBlockStart.gifContractedSubBlock.gif        
/**//// <summary>
InBlock.gif        
/// 寻找并返回某个指定的节点
InBlock.gif        
/// 如果找不到,返回null
InBlock.gif        
/// 递归方法
InBlock.gif        
/// </summary>
InBlock.gif        
/// <param name="searchMe">指定的节点的值</param>
ExpandedSubBlockEnd.gif        
/// <returns>要找的节点</returns>

InBlock.gif        public Tree<T> Search(T searchMe)
ExpandedSubBlockStart.gifContractedSubBlock.gif        
dot.gif{
InBlock.gif            
if (false == this.removed && 0 == this.data.CompareTo(searchMe)) // findMe == this.data,递归出口
ExpandedSubBlockStart.gifContractedSubBlock.gif
            dot.gif{
InBlock.gif                
return this;
ExpandedSubBlockEnd.gif            }

InBlock.gif            
else if (this.left != null && this.data.CompareTo(searchMe) > 0// findMe < this.data
ExpandedSubBlockStart.gifContractedSubBlock.gif
            dot.gif{
InBlock.gif                
return this.left.Search(searchMe);
ExpandedSubBlockEnd.gif            }

InBlock.gif            
else if (this.right != null && this.data.CompareTo(searchMe) < 0// findMe > this.data
ExpandedSubBlockStart.gifContractedSubBlock.gif
            dot.gif{
InBlock.gif                
return this.right.Search(searchMe);
ExpandedSubBlockEnd.gif            }

InBlock.gif            
else// 没找到。范围逐渐缩小到,缩小到左右子树均为null了还没有找到,则宣布没有找到。
ExpandedSubBlockStart.gifContractedSubBlock.gif
            dot.gif{
InBlock.gif                
return null;
ExpandedSubBlockEnd.gif            }

ExpandedSubBlockEnd.gif        }
// Search
InBlock.gif

ExpandedSubBlockStart.gifContractedSubBlock.gif        
/**//// <summary>
InBlock.gif        
/// 在控制台上按从小到大的顺序输出,每行一个元素
InBlock.gif        
/// 调试专用
InBlock.gif        
/// 递归方法
ExpandedSubBlockEnd.gif        
/// </summary>// Traverse

InBlock.gif        public void Traverse()
ExpandedSubBlockStart.gifContractedSubBlock.gif        
dot.gif{
InBlock.gif            
if (null != this.left)
ExpandedSubBlockStart.gifContractedSubBlock.gif            
dot.gif{
InBlock.gif                
this.left.Traverse();
ExpandedSubBlockEnd.gif            }

InBlock.gif
InBlock.gif            
if (false == this.removed) // 递归出口
ExpandedSubBlockStart.gifContractedSubBlock.gif
            dot.gif{
InBlock.gif                Console.WriteLine(
this.data.ToString());
ExpandedSubBlockEnd.gif            }

InBlock.gif
InBlock.gif            
if (null != this.right)
ExpandedSubBlockStart.gifContractedSubBlock.gif            
dot.gif{
InBlock.gif                
this.right.Traverse();
ExpandedSubBlockEnd.gif            }

ExpandedSubBlockEnd.gif        }
// Traverse
InBlock.gif

ExpandedSubBlockStart.gifContractedSubBlock.gif        
/**//// <summary>
InBlock.gif        
/// 接收一个参数的构造函数
InBlock.gif        
/// </summary>
ExpandedSubBlockEnd.gif        
/// <param name="data">根节点的值</param>

InBlock.gif        public Tree( T data )
ExpandedSubBlockStart.gifContractedSubBlock.gif        
dot.gif{
InBlock.gif            
this.data = data;
InBlock.gif            
this.left = null;
InBlock.gif            
this.right = null;
ExpandedSubBlockEnd.gif        }
// Tree
InBlock.gif
        
ExpandedSubBlockStart.gifContractedSubBlock.gif        
/**//// <summary>
InBlock.gif        
/// 接受多个参数或一个数组参数的构造函数
InBlock.gif        
/// </summary>
ExpandedSubBlockEnd.gif        
/// <param name="array">包含所有节点的值的数组</param>

InBlock.gif        public Tree(params T[] array) 
ExpandedSubBlockStart.gifContractedSubBlock.gif        
dot.gif{
InBlock.gif            
if (array.Length > 0)
ExpandedSubBlockStart.gifContractedSubBlock.gif            
dot.gif{
InBlock.gif                
this.data = array[0];
InBlock.gif                
for (int i = 1; i < array.Length; i++)
ExpandedSubBlockStart.gifContractedSubBlock.gif                
dot.gif{
InBlock.gif                    
this.Add(array[i]);
ExpandedSubBlockEnd.gif                }

InBlock.gif                
//人们用来构造二叉树的数组往往是有序的,
InBlock.gif                
//但这恰恰会让构造出的二叉树具有最低效的结构,
InBlock.gif                
//因此用数组构造完二叉树应该立即优化一下
InBlock.gif
                this.Optimize();
ExpandedSubBlockEnd.gif            }

InBlock.gif            
else
ExpandedSubBlockStart.gifContractedSubBlock.gif            
dot.gif{
InBlock.gif                
throw new Exception("cannot build a binary tree from an empty array");
ExpandedSubBlockEnd.gif            }
            
ExpandedSubBlockEnd.gif        }
// Tree
InBlock.gif

InBlock.gif
ExpandedSubBlockEnd.gif        
#endregion

InBlock.gif
ExpandedSubBlockEnd.gif    }
// Tree<T>
ExpandedBlockEnd.gif
}
//  namespace
None.gif


转载于:https://www.cnblogs.com/dc10101/archive/2007/01/01/609635.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值