数据结构与算法案例集-002-算法基础
来源:互联网 发布:卖牛仔裤的淘宝店铺 编辑:程序博客网 时间:2024/05/01 14:27
案例2-1 求解指定实数的正的平方近似根
通过求解二次方程的近似实数根学习迭代算法的一般设计思路。
源文件:calculate_square_root.cpp
#include <stdio.h>
//
//calculate_square_root:
// 使用迭代算法计算指定实数的平方近似根;
//参数:
// pdOutR: [in,out]保存计算的平方近似根;
// dInR: [in]需要计算平方根的正实数;
// dE: [in]计算精度;
// nTime: [in]迭代最大次数(-1时不检查迭代次数);
//返回:
// 如果超过迭代次数,则返回-1;
// 如果找到精确根,则返回1;
// 如果找到近似根,则返回0;
//
int calculate_square_root(double* pdOutR, double dInR, double dE,
unsigned long nTime)
{
double dX0 = 0.0;
double dX1 = 0.0;
double dMax = 0.0;
double dMin = 0.0;
double dPitch = 1.0;
double dVal = 0.0;
double dAbs = 0.0;
unsigned long nAcc = 0;
int nRlt = 0;
dX0 = 0.0;
dX1 = 0.0;
dMin = 0.0;
dPitch = 1.0;
nAcc = 0;
while(1){
//检查迭代次数;
nAcc ++;
if((nTime!=-1) && (nAcc>nTime))
{
nRlt = -1;
break;
}
dX1 = dX0 + dPitch;
dVal = dX1 * dX1 - dInR;
if(dVal == 0)
{
//相等则得到精确根;
nRlt = 1;
break;
}
else if(dVal < 0)
{
dX0 = dX1;
dMin = dX1;
}
else {
dMin = dX0;
dMax = dX1;
dPitch = (dMax-dMin)/2;
//检查计算精度;
dAbs = (dX0>=dX1)?(dX0-dX1):(dX1-dX0);
if(dAbs < dE)
{
nRlt = 0;
break;
}
}
}
*pdOutR = dX1;
return nRlt;
}
void main(void)
{
double dOutR = 0.0;
double dInR = 35221.93234;
double dE = 0.00000001;
unsigned long nAcc = -1;
calculate_square_root(&dOutR, dInR, dE, nAcc);
}
迭代法是一种以反复、循环、重述(或重复)为特征的解决问题的方法。计算一个实数的平方近似根需要指定一个精度(或迭代最大次数),在指定的限制条件下,通过上面的迭代方法可以获取得需要的平方近似根。
案例2-2 查找指定字符串的子串的位置
学习穷举搜索法的设计思路。
源文件:find_substring.cpp
#include <stdio.h>
#include <string.h>
//
//find_substring:
// 从指定字串中查找指定的子串;
//参数:
// pSrc: [in]目标字符串;
// pSub: [in]子字符串;
//返回:
// 子串的位置(或NULL);
//
char* find_substring(const char *pSrc, const char *pSub)
{
char* pRlt = NULL;
char* ps = NULL;
int nSrcLen = 0;
int nSubLen = 0;
int acc = 0;
int nEqu = 0;
//检查输入参数;
if(!pSrc || !pSub) return NULL;
//获取并比较字串的长度;
nSrcLen = strlen(pSrc);
nSubLen = strlen(pSub);
if(nSrcLen < nSubLen)
{
return NULL;
}
//依次对比字串;
for(ps=(char*)pSrc,acc=0; acc<=(nSrcLen-nSubLen); ps++,acc++)
{
nEqu = memcmp(ps, pSub, nSubLen);
if(nEqu == 0)
{
pRlt = ps;
break;
}
}
return pRlt;
}
void main(void)
{
char pSrc[] = "Hello,nice to meet you!";
char pSub1[] = "to ";
char pSub2[] = " you ";
char* pPos = NULL;
pPos = find_substring(pSrc, pSub1);
if(pPos != NULL)
{
printf("Found substring(%s) from (%s)./r/n", pSub1, pSrc);
}
else {
printf("Not Found substring(%s) from (%s)./r/n", pSub1, pSrc);
}
pPos = find_substring(pSrc, pSub2);
if(pPos != NULL)
{
printf("Found substring(%s) from (%s)./r/n", pSub2, pSrc);
}
else {
printf("Not Found substring(%s) from (%s)./r/n", pSub2, pSrc);
}
}
穷举搜索法是对可能的结果按某种顺序进行逐一枚举和检验,并从中找到那些符合要求的结果。使用穷举搜索法的重要的前题是可能的结果是有限个并且可以枚举。穷举搜索法的算法复杂度低,但计算量可能很大。
案例2-3 求N!
求整数N的阶乘学习递推法。
源代码:factorial.cpp
#include <stdio.h>
#include <stdlib.h>
#define MAX_ARRAY_DIGIT 200
#define MAX_N 100
int g_pnVal[MAX_ARRAY_DIGIT];
int main(int argc, char* argv[])
{
int nFTL = 0;
int nDigit = 0;
int nVal = 0;
int nP = 0;
int i = 0;
int j = 0;
//检查参数;
if(argc < 2)
{
printf("Usage: factorial N(2<=N<=100)/r/n");
return -1;
}
nFTL = atol(argv[1]);
if((nFTL<2) || (nFTL>MAX_N))
{
printf("Usage: factorial N(2<=N<=100)/r/n");
}
//递推计算和显示N的阶乘(已知(i-1)!,求i!);
g_pnVal[0] = 1;
nDigit = 1;
for(i=2; i<=nFTL; i++)
{
//用i依次乘(i-1)!的各位(可能需要进位);
for(nP=0,j=0; j<nDigit; j++)
{
nVal = g_pnVal[j] * i + nP;
g_pnVal[j] = nVal%10;
nP = nVal/10;
}
//处理最后的进位;
while(nP)
{
g_pnVal[nDigit++] = nP%10;
nP /= 10;
}
//显示计算的结果;
printf("%02d!= ", i);
for(j=nDigit-1; j>=0; j--)
{
printf("%d", g_pnVal[j]);
}
printf("/r/n");
//检查是否超界;
if(nDigit >= MAX_ARRAY_DIGIT-1)
{
printf("overlay the array, exited!/r/n");
break;
}
}
return 0;
}
递推法是利用问题本身所具有的一种递推关系解决问题的一种方法。使用递推法求整数N的阶乘(N!=N*(N-1)*(N -2)*…*3*2*1)。N的阶乘有一个明显的递推关系:N!=N*(N-1)!。依次求出1!、2!、3!、…、(N-1) !,则可以得到N!。
计算机的整数位数是有限的,N!的结果很可能超过最大的整数(如128位),所以算法设计中,使用数组来存储大整数i!。为了方便,设数组的每个元素存放大整数的一位数字,并将整数i!自个位到高位顺序存储于数组中。
案例2-4 找出从自然数1到N中任取R个数的所有组合
学习递归算法和回溯算法。
源代码:comb_integer.cpp
#include <stdio.h>
#include <stdlib.h>
#define MAX_N 100
int g_pnA[MAX_N];
int g_nR = 0;
int comb_integer(int nM, int nK)
{
int i = 0;
int j = 0;
//递归方法计算组合问题;
for(i=nM; i>=nK; i--)
{
g_pnA[nK-1] = i;
if(nK > 1)
{
//组合中还有其它元素;
comb_integer(i-1, nK-1);
}
else {
//输出获取的给合;
for(j=g_nR-1; j>=0; j--)
{
printf("%4d", g_pnA[j]);
}
printf("/r/n");
}
}
return 0;
}
int main(int argc, char* argv[])
{
int nM = 0;
int nK = 0;
//检查参数;
if(argc < 3)
{
printf("Usage: comb_integer N(2<N<100) K(K<=N)/r/n");
return -1;
}
nM = atol(argv[1]);
nK = atol(argv[2]);
if((nM<2) || (nM>MAX_N) || (nK<1) || (nK>nM))
{
printf("Usage: comb_integer N(2<=N<=100) K(K<=N)/r/n");
}
g_nR = nK;
//递归计算组合;
comb_integer(nM, nK);
return 0;
}
当组合的第一个数据选定时,其后的数据是从余下的m-1个数中取k-1个数的组合。从而将求m个数中取k个数的组合问题,转化成求m-1个数中取k-1个数的组合问题。
函数引入工作数据a[]存放求出的组合的数字,约定函数将确定的k.个数字组合的第一个数字放在a[k-1]中,当一个组合求出后,才将a[]中的一个组合输出。第一个数可以是m,m-1,……,k,函数将确定的组合的第一个数字放入数组后,有两种可能的选择:若还未确定组合的其余元素,则继续递归去确定组合的其余元素;若已确定了组合的全部元素,则输出这个组合。
源文件:comb_integer_back.cpp
#include <stdio.h>
#include <stdlib.h>
#define MAX_N 100
int g_pnA[MAX_N];
int g_nR = 0;
int comb_integer_back(int nM, int nK)
{
int i = 0;
int j = 0;
//回溯方法计算组合问题;
i = 0;
g_pnA[i] = 1;
while(1){
if(g_pnA[i]-i <= nM-nK+1)
{
//向前试探;
if(i == nK-1)
{
//找到一个组合;
for(j=0; j<nK; j++)
{
printf("%4d", g_pnA[j]);
}
printf("/r/n");
g_pnA[i] ++;
continue;
}
//向前试探;
i ++;
g_pnA[i] = g_pnA[i-1] +1;
}
else {
//回溯;
if(i == 0)
{
//找完全部解;
break;
}
i --;
g_pnA[i] ++;
}
}
return 0;
}
int main(int argc, char* argv[])
{
int nM = 0;
int nK = 0;
……
//回溯计算组合;
comb_integer_back(nM, nK);
return 0;
}
采用回溯法找问题的解,将找到的组合以从小到大顺序存放于A[0],A[1],…,A[R-1],组合的元素满足以下性质:
(1)A[I+1]>A[I],即后一个数字比前一个大;
(2)A[I]-I <= N-R+1。
假设N=5,R=2。按回溯算法思想,找解的过程可以叙述如下:首先放弃组合数个数为R的条件,候选组合从只有一个数字1开始。因该候选解满足除问题规模之外的全部条件,扩大其规模,并使用其满足上述条件(1),候选组合改为1,2。继续这一过程,得到候选组合1,2,3。该候选解满足包括问题规模在内的全部要求,因而是一个解。在该解基础上,选下一个候选解,因A[2]上的3调整为4,以及以后调整为5都满足问题的全部要求,得到解1,2,4和1,2,5。由于对5不能再作调整,就要从A[2]回溯到A[1],这时的A[1]=2,可以调整为3,并向前试探,得到解1,3,4。重复上述向前试探和向后回溯的过程,直至要从A[0]再回溯时,说明已找完问题的全部解。
案例2-5 用贪婪法处理装箱问题
学习贪婪法处理问题。
源代码:box_volume.cpp
////////////////////////////////////////////////////////////////////////
//
// box_volume.cpp: 使用贪婪法处理装箱问题案例源代码;
//
////////////////////////////////////////////////////////////////////////
#include <stdio.h>
#include <stdlib.h>
//物品信息结点;
typedef struct _ElmNodeTags
{
int nNO; //物品的编号;
int nVolume; //物品的体积;
struct _ElmNodeTags* link; //后继物品的指针;
}ELMNODE;
//箱子信息结点;
typedef struct _BoxNodeTags
{
int nRemainder; //箱子可用空间;
ELMNODE* head; //箱内物品表头;
ELMNODE* tail; //箱内物品表尾;
struct _BoxNodeTags* next; //后继箱子指针;
}BOXNODE;
int main(int argc, char* argv[])
{
BOXNODE* pBoxHead = NULL;
BOXNODE* pBoxTail = NULL;
BOXNODE* pBox = NULL;
BOXNODE* pBoxT = NULL;
ELMNODE* pElmHead = NULL;
ELMNODE* pElmTail = NULL;
ELMNODE* pElm = NULL;
ELMNODE* pElmT = NULL;
int good_count = 0;
int box_count = 0;
int box_volume = 0;
int vol = 0;
int n = 0;
int i = 0;
//获取输入信息;
printf("Input the Box Volume: /r/n");
scanf("%d", &box_volume);
printf("Input the Count of Goods: /r/n");
scanf("%d", &good_count);
//输入所有的物品的体积(由大到小);
printf("Input the volumes of goods ordered by high-to-low:/r/n");
for(i=0; i<good_count; i++)
{
scanf("%d", &vol);
pElm = (ELMNODE*)malloc(sizeof(ELMNODE));
if(pElm == NULL)
{
printf("Error memory, failed!/r/n");
return 0;
}
pElm->link = NULL;
pElm->nVolume = vol;
pElm->nNO = i;
//加入到物品表中;
if(pElmHead == NULL)
{
pElmHead = pElm;
pElmTail = pElm;
}
else {
pElmTail->link = pElm;
pElmTail = pElm;
}
}
//采用贪婪法处理装箱问题(每个箱子尽可能的装入物品);
pBoxHead = NULL;
pBoxTail = NULL;
box_count = 0;
while(1){
//取出物品表头的数据;
pElm = pElmHead;
if(pElm == NULL)
{
break;
}
pElmHead = pElmHead->link;
if(pElmHead == NULL)
{
pElmTail = NULL;
}
pElm->link = NULL;
vol = pElm->nVolume;
//查找可用的箱子;
for(pBox=pBoxHead; pBox!=NULL; pBox=pBox->next)
{
if(pBox->nRemainder >= vol)
{
break;
}
}
//如果没有箱子,则创建新的并加入箱表中;
if(pBox == NULL)
{
pBox = (BOXNODE*)malloc(sizeof(BOXNODE));
if(pBox == NULL)
{
printf("Error Memory,Failed!/r/n");
return -1;
}
pBox->nRemainder = box_volume - vol;
pBox->head = NULL;
pBox->tail = NULL;
pBox->next = NULL;
//加入到箱子表中;
if(pBoxHead == NULL)
{
pBoxHead = pBox;
pBoxTail = pBox;
}
else {
pBoxTail->next = pBox;
pBoxTail = pBox;
}
box_count ++;
}
//如果可用,则加入到箱子中;
else {
pBox->nRemainder -= vol;
}
//把物品转移到箱子中;
if(pBox->head == NULL)
{
pBox->head = pElm;
pBox->tail = pElm;
}
else {
pBox->tail->link = pElm;
pBox->tail = pElm;
pElm->link = NULL;
}
}
//输出计算的结果;
printf("total box: %d/r/n", box_count);
printf("goods in thd boxes were: /r/n");
for(pBox=pBoxHead,i=0; pBox!=NULL; pBox=pBox->next,i++)
{
printf(" %2d th box, leave volume is %4d, the goods were:/r/n",
i, pBox->nRemainder);
for(pElm=pBox->head; pElm!=NULL; pElm=pElm->link)
{
printf("%4d(%d)", pElm->nNO+1, pElm->nVolume);
}
printf("/r/n");
}
//释放缓冲区;
for(pBox=pBoxHead; pBox!=NULL; )
{
pBoxT = pBox->next;
for(pElm=pBox->head; pElm!=NULL; )
{
pElmT = pElm->link;
free(pElm);
pElm = pElmT;
}
pBox->head = NULL;
pBox->tail = NULL;
free(pBox);
pBox = pBoxT;
}
return 0;
}
装箱问题可简述如下:设有编号为0,1,…,N-1的N种物品,体积分别为V(0),V(1),…,V(N-1)。将这N种物品装入容量都为V的若干箱子里。约定这N种物品的体积均不超过V,即对于0≤I≤N,有0<V(I)<V。不同的装箱方案所需要的箱子数目可能不同,装箱问题要求使装尽这N种物品的箱子数要少。
案例2-6 为参加网球比赛的选手安排循环比赛日程
学习分治算法和回溯算法的设计技术。
源代码:game_date.cpp
////////////////////////////////////////////////////////////////////////
//
// game_date.cpp: 使用分治法/回溯法处理比赛的日程安排;
//
////////////////////////////////////////////////////////////////////////
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>
#define MAX_N 256 //最大的比赛人数(2的8次幂);
int g_pnA[MAX_N+1][MAX_N]; //比赛的列表;
int g_pnS[MAX_N][MAX_N]; //回溯的状态列表;
//
//使用分治法计算比赛日程表(比赛人数为2的nK次幂);
//
int game_data_pice(int nK)
{
int twom1 = 0;
int twom = 0;
int col = 0;
int row = 0;
int m = 0;
//预设两位选手的比赛日程;
g_pnA[1][1] = 2;
g_pnA[2][1] = 1;
m = 1;
twom1 = 1;
//为2的m次幂的选手安排比赛日程;
while(m < nK)
{
m ++;
twom1 += twom1;
twom = 2*twom1;
//填日程表的左下角;
for(row=twom1+1; row<=twom; row++)
{
for(col=1; col<=twom1-1; col++)
{
g_pnA[row][col] = g_pnA[row-twom1][col] + twom1;
}
}
//填日程表右上角的第1列;
g_pnA[1][twom1] = twom1 + 1;
for(row=2; row<=twom1; row++)
{
g_pnA[row][twom1] = g_pnA[row-1][twom1] + 1;
}
//填日程表右上角的其它列(参照前一列填当前列);
for(col=twom1+1; col<twom; col++)
{
for(row=1; row<twom1; row++)
{
g_pnA[row][col] = g_pnA[row+1][col-1];
}
g_pnA[twom1][col] = g_pnA[1][col-1];
}
//参照右上角填日程表的右下角;
for(col=twom1; col<twom; col++)
{
for(row=1; row<=twom1; row++)
{
g_pnA[g_pnA[row][col]][col] = row;
}
}
//输出当前级别的日程表;
for(row=1; row<=twom; row++)
{
for(col=1; col<twom; col++)
{
printf("%4d", g_pnA[row][col]);
}
printf("/r/n");
}
printf("/r/n");
}
return 0;
}
//
//使用回溯法计算比赛日程表(nM为人数,当nAll为1时,获取所有的组合);
//
int game_data_back(int nM, int nAll)
{
int col = 0;
int row = 0;
int val = 0;
int has = 0;
int i = 0;
int j = 0;
//清理状态位;
memset(g_pnS, 0, sizeof(int)*MAX_N*MAX_N);
//从A[0][0]位置开始回溯检查;
row = 0;
col = 0;
while(1){
//设置A[row][col]的元素,并检查是否重复;
if(g_pnS[row][col] == 1)
{
val = g_pnA[row][col] + 1;
}
else {
val = 1;
}
for(; val<=nM; val++)
{
//检查是否与该行前面的元素相同;
for(has=0,j=0; j<=col-1; j++)
{
if(g_pnA[row][j] == val)
{
has = 1;
break;
}
}
//检查是否与该列前面的元素相同;
for(i=0; (i<=row-1)&&(has==0); i++)
{
if(g_pnA[i][col] == val)
{
has = 1;
break;
}
}
if(has == 0)
{
break;
}
}
//如果没有可用的元素,则向后回溯;
if(val > nM)
{
g_pnS[row][col] = 0;
//如果没有可用的元素,则回溯到前一个元素;
if(row==0 && col==0)
{
//回溯到首位,全部结束;
break;
}
else if(col == 0)
{
//回退到上一行的尾列;
row --;
col = nM-1;
}
else {
//回退到同行的前一列;
col --;
}
g_pnS[row][col] = 1;
}
//如果找到可用的元素,则向前试探;
else {
g_pnA[row][col] = val;
g_pnS[row][col] = 1;
if(row==nM-1 && col==nM-1)
{
//到行和列的尾部,找到一个组合;
for(i=0; i<nM; i++)
{
for(j=0; j<nM; j++)
{
printf("%4d", g_pnA[i][j]);
}
printf("/r/n");
}
printf("/r/n");
//检查是否获取所有的组合;
if(nAll == 1)
{
g_pnS[row][col] = 1;
continue;
}
else {
break;
}
}
else if(col == nM-1)
{
//前进到下一行的首列;
row ++;
col = 0;
}
else {
//前进到同行的后一列;
col ++;
}
g_pnS[row][col] = 0;
}
}
return 0;
}
int main(int argc, char* argv[])
{
int nK = 0;
int nM = 0;
//检查参数;
if(argc < 2)
{
printf("Usage: game_data N(2<N<8)/r/n");
return -1;
}
nK = atol(argv[1]);
if((nK<2) || (nK>8))
{
printf("Usage: game_data N(2<N<8)/r/n");
return -1;
}
nM = (int)pow(2, nK);
//分治法计算组合;
memset(g_pnA, 0, sizeof(int)*(MAX_N+1)*MAX_N);
game_data_pice(nK);
//回溯法计算组合;
memset(g_pnA, 0, sizeof(int)*(MAX_N+1)*MAX_N);
game_data_back(nM, 0);
return 0;
}
上例中,通过分治法和回溯法解决同样的问题。不同的算法,计算复杂度和效率会有很大的不同。针对比赛日程表,分治法得到一个排列结果,回溯法可以得到所有的排列结果。算法的目的是得到正确的结果,有时候也需要计究效率。
案例2-7 求最长公共字符子序列
学习动态规划算法的设计技术。
源代码:string_maxsubs.cpp
////////////////////////////////////////////////////////////////////////
//
// string_maxsubs.cpp: 使用动态规划法求最长公共字符子序列;
//
////////////////////////////////////////////////////////////////////////
#include <stdio.h>
#include <string.h>
#define MAX_N 100 //字符串的最大字节长度;
int g_pnC[MAX_N][MAX_N]; //公共子序列的长度数组;
//
//使用动态规划法求最长公共字符子序列;
//
int string_max_subs(char* pstrA, char* pstrB, char* pstrO, int nLenO)
{
int nLenA = 0;
int nLenB = 0;
int nLenC = 0;
int i = 0;
int j = 0;
int k = 0;
//计算最长公共子序列的长度;
nLenA = strlen(pstrA);
nLenB = strlen(pstrB);
g_pnC[0][0] = 0;
for(i=1; i<=nLenA; i++)
{
g_pnC[i][0] = 0;
}
for(j=1; j<=nLenB; j++)
{
g_pnC[0][j] = 0;
}
for(i=1; i<=nLenA; i++)
{
for(j=1; j<=nLenB; j++)
{
if(pstrA[i-1] == pstrB[j-1])
{
g_pnC[i][j] = g_pnC[i-1][j-1] + 1;
}
else if(g_pnC[i-1][j] >= g_pnC[i][j-1])
{
g_pnC[i][j] = g_pnC[i-1][j];
}
else {
g_pnC[i][j] = g_pnC[i][j-1];
}
}
}
nLenC = g_pnC[nLenA][nLenB];
//根据生成数组及长度反向构造序列;
pstrO[nLenC] = '/0';
i = nLenA;
j = nLenB;
k = nLenC;
while(k > 0)
{
if(g_pnC[i][j] == g_pnC[i-1][j])
{
i -- ;
}
else if(g_pnC[i][j] == g_pnC[i][j-1])
{
j -- ;
}
else {
k --;
pstrO[k] = pstrA[i-1];
i --;
j --;
}
}
return 0;
}
int main(int argc, char* argv[])
{
char strO[MAX_N];
char strA[] = "Usage: string_maxsubs.cpp";
char strB[] = "Usge: strng_maxsubs.cp";
char strC[] = " subs.Usage :";
memset(strO, 0, sizeof(strO));
string_max_subs(strA, strB, strO, sizeof(strO));
printf("(%s) and (%s):/r/n common max-length-substring is: (%s)/r/n",
strA, strB, strO);
printf("/r/n");
memset(strO, 0, sizeof(strO));
string_max_subs(strA, strC, strO, sizeof(strO));
printf("(%s) and (%s):/r/n common max-length-substring is: (%s)/r/n",
strA, strC, strO);
printf("/r/n");
return 0;
}
定义C[I][J]为序列(A0,A1,…,AI)和(B0,B 1,…,BJ)的最长公共子序列的长度,则计算C[I][J]可递归地表述如下:
C[I][J]=0,如果I=0或J=0;
C[I][J]= C[I-1][J-1]+1,如果I,J>0,且A[I-1]==B[J-1];
C[I][J]=MAX(C[I][J-1],C[I-1][J]),如果I,J>0,且A[I-1]!=B[J-1] 。
根据上述算式可以得到计算两个序列最长公共子序列的长度的函数。可以看出,C[I][J]的产生仅依赖于C[I-1][J-1]、C[I-1][J]和C[I][J-1]。利用获取最长公共子序列长度的计算过程所产生的数组C[][],可以从C[M][N]开始,跟踪C[I][J]的产生方法,反向构造出最长公共子序列。
- 数据结构与算法案例集-002-算法基础
- 数据结构与算法案例集-001-数据结构基础
- 数据结构与算法基础
- 数据结构与算法基础
- 基础数据结构与算法
- 数据结构与算法基础
- 【数据结构与算法基础】序
- 数据结构与算法基础1
- 数据结构与算法基础总结
- 数据结构与算法基础总结
- 基础数据结构与算法--序
- 数据结构基础与常见算法
- 算法基础:数据结构与算法入门概览
- 数据结构与算法-基础数据结构(链表)
- 数据结构与算法学习总结-数据结构基础
- 数据结构基础与基础算法总结
- 算法基础---枚举案例
- 数据结构与算法 -- 算法
- SQL Server 2005 不允许远程连接解决方法
- wxWidget类型转换
- 使用ClickOne发布Windows应用程序
- 数据结构与算法案例集-001-数据结构基础
- 应用DIV+CSS编码时容易犯的一些错误
- 数据结构与算法案例集-002-算法基础
- java新手学习使用FtpClient
- Windows延缓写入失败
- Administering and Securing the Apache Server
- Python Programming on WIN32
- IBM推新刀片服务器 着重加强安全性
- Java Web Services
- Database in Depth : Relational Theory for Practitioners
- Chord阅读报告