2 数据结构.docx
《2 数据结构.docx》由会员分享,可在线阅读,更多相关《2 数据结构.docx(27页珍藏版)》请在冰豆网上搜索。
2数据结构
2数据结构
根据逻辑关系,数据结构可分为4类:
集合、线性结构、数结构、图结构。
两种存储方式:
顺序存储结构和链接存储结构。
2.1算法概述
算法是对特定问题求解步骤的一种描述,算法的5个重要特性:
输入、输出、
有穷性、确定性和可行性。
一个“好”算法还需要具有下列特性:
正确性、鲁棒
性、简单性、抽象分级和高效性。
伪代码是“算法语言”。
1.算法设计的一般原则:
理解问题。
准确的理解算法的输入是什么?
要求算法完成什么?
即明确
算法的入口和出口。
预测所有可能的输入。
包括合法的输入和非法的输入。
列举出算法必须
处理的所有情况。
抽象分级,使解决方案模块化。
可以有效的帮助我们解决复杂问题。
跟踪代码。
发现算法中的逻辑错误的唯一重要的方法就是系统的跟踪算
法。
2.算法时间复杂度的分析方法
找出算法中的基本语句,执行次数最多的语句就是基本语句,通常是最
内层循环的循环体。
计算基本语句执行次数的数量级。
用大O极好表示算法的时间性能。
如果算法中包含嵌套的循环,则基本
语句通常是最内层的循环体,如果算法中包含并列的循环,则将并列循环的时间
复杂度相加。
2.2线性表(重要)
线性表可以顺序表、单链表、循环表、双链表和静态链表等。
2.2.1单链表
编写用来删除单链表的头元素的函数。
voidRemoveHead(node**head)
{
node*tmp;
//iftheheadisaNULL,donothing.
if(!
(*head))
return;
tmp=(*head)->next;
free(*head);
*head=tmp;
return;
}
函数检查步骤
..查看输入的参数是否合法;
..考虑通常情况下的操作是否达到要求;
..考虑返回值是否达到要求;
..检查异常情况下函数的功能;
..最后检查函数的正确性需要满足如下三个条件:
1.能够完成要求的工作;
2.能够被正确调用,能够产生正确的返回值;
3.能够对异常情况作出适当处理。
1.删除单链表的倒数第m个元素,算法需要即节省时间又节省空间,并对异
常情况做出适当的处理。
解决思路:
单链表,显然我们无法知道其长度n,因此在确定倒数第m个元
素之前,我们首先要找到该链表的尾指针。
那么首先,可以想到的一个算法就是
先遍历该单链表,计算该链表的长度n,然后,我们可以确定倒数第m个元素的
位置为(n-m),然后,再重新从链表的头开始搜索,找到(n-m)个元素后,就
是需要找的。
这种方法的时间复杂度:
O(n)。
空间复杂度:
就是一个辅助指针。
那么这是不是最节省时间和空间的呢?
不见得,我们还可以增加一个辅助指
针变量,使其与遍历指针保持m个距离,即当“前导指针”遍历到第m个元素
时,启动“拖后指针”,使“拖后指针”指向本链表的头指针,然后与“前导指
针”同步移动,那么,当“前导指针”指向链表末时,此时“拖后指针”当前指
向的元素即为“倒数第m个”元素。
找到需要的元素。
这种算法的时间复杂度:
O(n),但是要比前一种方法节省了越一般的时间,因为,前一种方法需要对链表
进行两次遍历,而后这只需要遍历一遍,付出的代价仅仅是增加了一个指针变量。
所以是值得的。
因此,我们可以选择第二种方法。
下面,我们来讨论一下本算法的函数原型:
一般来说,我们总是希望函数的
返回值能够是要找的“倒数第m个”元素,但是,考虑到可能出现的异常情况:
寻找失败,所以,我们可以,令函数返回值为一个错误码,即寻找成功返回1,
失败返回0,然后通过一个指向指针的指针参数来获得所要找的元素。
代码如下,
第一段代码的语义不够简洁,因此我又对其改进,如第二段代码。
//第一种实现代码。
intFindRevM(nodeconst*head,void**getm,intm)
{
node*pre=head;
node*behind=NULL;
inti=0;
if(head==NULL)
{return0;}
while(pre)
{
if(i{
i++;
pre=pre->next;
}
if(i==m)
{
behind=head;
}
pre=pre->next;
behind=behind->next;
}
if(i{return0;}
*getm=behind;
return1;
}
//第二种实现代码。
intFindRevM(nodeconst*head,void**getm,intm)
{
node*pre=head;
node*behind=NULL;
inti=0;
if(head==NULL)
{return0;}
for(i=0;i{
if(pre->next)
{
pre=pre->next;
}
//iftheprepointerreachtheendofchain
//beforeireachsm,returnfalse.becausethe
//numberofnodesonthechainislessthanm.
else
{return0;}
}
//startthebehindpointer.
behind=head;
while(pre->next)
{
pre=pre->next;
behind=behind->next;
}
//getm.
*getm=behind;
return1;
}
2.3特殊线性表–堆栈、队列和串(重要)
2.3.1堆栈
栈是限定在表尾进行插入和删除操作的线性表,栈中元素除了具有线性关系外,还具有
先进后出(LIFO,LastinFirstout)的特性。
用C++代码实现堆栈类:
Classstack
{
public:
stack();
~stack();
Voidpush(void*data)
Voidpop();
Protected:
//Elementstructneededonlyinternally.
TypedefstructelementT{
StructelementT*next;
Void*data;
}elementT;
elementT*header;
};
//constructureofstackclass.
stack:
:
stack()
{
Header=NULL;
Return;
}
Stack:
:
~stack()
{
elementT*tmp;
While(header)
{
tmp=header->next;
free(header);
header=tmp;
}
Return;
}
Stack:
:
push(void*data)
{
elementT*tmp=newelementT;
tmp->data=data;
tmp->next=header;
header=tmp;
return;
}
Void*stack:
:
pop()
{
elementT*tmp;
void*data;
assert(header!
=NULL);
data=header->data;
tmp=header->next;
free(header);
header=tmp;
returndata;
}
2.3.2堆(heap)和堆栈(stack)
heap:
是由malloc之类函数分配的空间所在地。
地址是由低向高增长的。
stack:
是自动分配变量,以及函数调用的时候所使用的一些空间。
地址是由
高向低减少的。
一个由c/C++编译的程序占用的内存分为以下几个部分:
①栈区(stack)
由编译器自动分配释放,存放函数的参数值,局部变量的值等。
其操作方
式类似于数据结构中的栈。
②堆区(heap)
一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收。
注意它与数据结构中的堆是两回事,分配方式倒是类似于链表,呵呵。
③全局区(静态区)(static)
全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在
一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。
-
程序结束后有系统释放
④文字常量区
常量字符串就是放在这里的。
程序结束后由系统释放
⑤程序代码区
存放函数体的二进制代码。
2.3.3队列
队列是只允许在一端进行插入操作、而在另一端进行删除操作的线性表,队
列中的元素除了具有线性关系外,还具有先进先出(FIFO,FirstinFirstout)的
特性。
2.3.4字符串
串拷贝(strcpy)和内存拷贝(memcpy)有什么不同?
它们适合于在哪种情况下使用?
①strcpy()函数只能拷贝字符串。
strcpy()函数将源字符串的每个字节拷贝到
目录字符串中,当遇到字符串末尾的null字符(\0)时,它会删去该字符,并结束
拷贝。
②memcpy()函数可以拷贝任意类型的数据。
因为并不是所有的数据都以null
字符结束,所以你要为memcpy()函数指定要拷贝的字节数。
③在拷贝字符串时,通常都使用strcpy()函数;在拷贝其它数据(例如结构)
时,通常都使用memcpy()函数。
④在实现的时候需要考虑内存重叠。
数组编程试题:
删除特定字符
用C语言编写一个高效率的函数来删除字符串中特定的字符,函数调用模型
如下:
voidRemoveChars(char*str,char*remove)
{
intsrc,dst,removeArray[256];
//256ASSICcharacters,
//Zeroallelementsinarray.
for(src=0;src<256;src++)
{
removeArray[src]=0;
}
//markthecharstoberemoved.
src=0;
while(remove[src])
{
removeArray[remove[src]]=1;
src++;
}
//Copycharunlessitmustberemoved.
src=dst=0;
do{//do...whileterminatesafterNUL.
if(!
removeArray[str[src]]
{
str[dst++]=str[src];
}
}while(str[src++]);}
2.4树和二叉树
注意二叉树节点的五种形态
空、没有子树、只有左子树、只有右子树、左右子树
存储结构
..顺序存储结构
..链式存储结构
二叉链表、三叉链表
structnode
{
intvalue;
node*left;
node*right;
};
二叉树的创建
node*creat(inta[],intlength)
{
inti;
node*root=NULL;
for(i=0;i{
root=Insert(root,a[i]);
}
returnroot;
}
插入
node*Insert(node*root,intvalue1)
{
node*np;
if(root==NULL)
{
np=newnode;
np->value=value1;
np->right=NULL;
np->left=NULL;
root=np;
returnroot;
}
if(value1<=root->value)
{
if(root->left==NULL)
{
np=newnode;
np->value=value1;
np->right=NULL;
np->left=NULL;
root->left=np;
returnroot;
}
else
{
root->left=Insert(root->left,value1);
}
}
else
{
if(root->right==NULL)
{
np=newnode;
np->value=value1;
np->right=NULL;
np->left=NULL;
root->right=np;
returnroot;
}
else
{
root->right=Insert(root->right,value1);
}
}
returnroot;
}
删除
需要考虑集中情况,若删除的节点为:
..叶子:
直接删除,修改父节点指针;
..父节点:
..若只有一个子树:
子节点代替其位置
..若有两个子树――
..从左子树,向右找到一个右子树为空的节点,将该节点移到
删除节点位置,用其左子树代替其原来位置。
..又分为以下两种情况:
105->44231513
和
voiddelTree(node*root,intvalue1)
{
//first,findit
node*p,*q;
q=NULL;//qistheparentnodeofp
p=root;
while(p&&(p->value!
=value1))
{
q=p;
if(value1>p->value)
p=p->right;
else
p=p->left;
}
if(p==NULL)
{
cout<<"cannotfindthisval"<return;
}
//checkthetypeofthenode
if((p->left==NULL)&&(p->right==NULL))
{
if(q==NULL)
root=NULL;
elseif(value1>q->value)
q->right=NULL;
else
q->left=NULL;
deletep;
return;
}
elseif((p->left!
=NULL)&&(p->right==NULL))
{
if(q==NULL)//therootnode
root=root->left;
elseif(value1value)
q->left=p->left;
else
q->right=p->left;
deletep;
return;
}
elseif((p->right)&&(p->left==NULL))
{
if(q==NULL)
root=p->right;
elseif(value1>q->value)
q->left=p->right;
else
q->right=p->right;
deletep;
return;
}
else
{
node*s;
q=NULL;
s=p->left;
//findthebiggestnodelowerthanit
while(s->right)
{
q=s;
s=s->right;
}
if(q==NULL)
p->left=s->left;
else
q->right=s->left;
p->value=s->value;
p=s;
deletep;
return;
}
}
遍历
voidPreOderTraverse(node*root)
{
if(root==NULL)
return;
cout<value<if(root->left!
=NULL)
PreOderTraverse(root->left);
if(root->right!
=NULL)
PreOderTraverse(root->right);
}
测试深度
intDeepOfTree(node*root)
{
intdeep1,deepL,deepR;
if(root==NULL)
return0;
deepL=DeepOfTree(root->left);
deepR=DeepOfTree(root->right);
deep1=deepL>deepR?
deepL:
deepR;
returndeep1+1;
}
2.5图
(该部分不是特别重要)
图的存储结构
..数组表示
..邻接表
..十字链表
图的遍历
..深度优先DFS
..广度优先BFS
最小生成树
..Prim算法
..Kruskal算法
最短路径问题
2.6查找技术
静态查找:
不涉及插入和删除操作的查找。
动态查找:
涉及插入和删除操作的查找。
用平均查找长度衡量查找算法的时间性能。
折半查找判定树、时空权衡、平衡二叉树。
2.7排序技术(重要)
2.7.1排序算法比较
排序算法的稳定性:
具有相同键值的记录经过排序后其相对次序保持不变,
则稳定。
否则,不稳定。
1.插入排序
将记录插入到已排序序列的正确位置上。
分类:
..直接插入排序:
..依次将待排序序列中的每一个记录插入到一个已排好序的序列中。
..时间复杂度:
最好情况,最坏情况,平均情况。
()On2()On2()On
..希尔排序:
..直接插入排序的改进。
先将待排序记录序列分割成若干子序列,在子
序列内分别进行直接插入排序,最后对全体记录进行一次直接插入排
序。
..时间复杂度:
~。
(log)Onn2()On
2.交换排序
将两个记录进行比较,当反序时进行交换。
..冒泡排序:
..两两比较相邻记录的关键字,如果反序则交换,直到没有反序的记录
为止。
..时间复杂度:
最好,最坏,平均。
()On2()On2()On
..快速排序:
..冒泡排序的改进,选定轴值,将待排序记录分割成独立的两部分,左
侧记录的关键码都小于等于轴值,右侧记录都大于等于轴值,然后分
别对左右两部分再分别重复上述过程,直到整个序列有序。
..时间复杂度:
平均。
(log)Onn
3.选择排序
选择排序:
从未排序序列中选出最小记录放入已排序队列的一端。
..简单选择排序:
..第i趟通过次关键码的比较,在ni.1ni.+(11)in≤≤.个记录中选取关
键码最小的,并通过与第i个记录交换最为有序序列的第个记录。
i
..时间复杂度:
最好、最坏、平均都是。
2()On
..堆排序:
..简单选择排序的改进。
首先将待排序序列构造成一个堆,然后选择堆
中最大的记录即堆顶,从堆中移走,并将剩下的记录再调整成堆,找
出其中最大的记录,再移走,以此类推。
..时间复杂度:
最好、最坏、平均都是。
(log)Onn
4.归并排序
归并排序:
将有序序列进行合并。
..二路归并排序:
将若干个有序序列两两归并,直至所有待排序记录都在
一个有序序列为止。
..时间复杂度:
最好、最坏、平均都是。
(log)Onn
2.7.2插入排序
..直接插入排序
把数组A[n]中待排序的n个元素看成一个有序表和一个无序表,开始时,
有序表中只包含一个元素A[0],无序表中包含n-1个元素A[1]~A[n-1],排序的
过程是每次从无序表中取出一个元素,把它插入到有序表中的适当位置,使之成
为一个新的有序表。
这样经n-1次插入后,无序表变成空表,有序表就包含了全
部n个元素,排序完毕。
技巧:
在排序的过程中:
将元素的比较和移动结合在一起。
voidinsertsort(int*a,intn)
{
intj;intx;
for(inti=1;i<=n-1;i++)
{
x=a[i];
for(j=i-1;j>=0;j--)
{
if(a[j]>x)
a[j+1]=a[j];
else
break;
}
a[j+1]=x;//注意这句不能与break放在一起
}
}
..希尔排序:
首先以的()为步长,把数组A中的个元素分为个组,使
下标距离为的元素同在一个组中,即A[0],A[d1],A[2d1]…的n个元素为第
一组,A[1],A[d1+1],A[2d1+1]…为第二组,…,接着在每一个组内进行直接
插入排序,然后以
1d10dn<<.n1d1d2d()21dd<为步长,在上一步的基础上,把A中的n个元素重
新分成d个组,使下标为的元素在同一组中,接着再在每个组内进行直接插入
排序,依次类推,直到dt=1。
d
voidshellsort(int*a,intn)
{
intx,i,j,d;
for(d=n/2;d>=1;d/=2)//步长,一般选为质数
{
for(i=d;i