2 数据结构.docx

上传人:b****8 文档编号:30129411 上传时间:2023-08-05 格式:DOCX 页数:27 大小:22.80KB
下载 相关 举报
2 数据结构.docx_第1页
第1页 / 共27页
2 数据结构.docx_第2页
第2页 / 共27页
2 数据结构.docx_第3页
第3页 / 共27页
2 数据结构.docx_第4页
第4页 / 共27页
2 数据结构.docx_第5页
第5页 / 共27页
点击查看更多>>
下载资源
资源描述

2 数据结构.docx

《2 数据结构.docx》由会员分享,可在线阅读,更多相关《2 数据结构.docx(27页珍藏版)》请在冰豆网上搜索。

2 数据结构.docx

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

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 党团工作 > 入党转正申请

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1