数据结构

一:绪论
1 :数据结构及算法的概念:
数据结构: 数据结构作为一门学科,主要研究数据的各种逻辑结构,还有物理结构以及对数据的各种操作,这包括三个方面:数据的逻辑结构,物理结构还有对数据的操作
他是相互之间存在一种或多种特定关系的数据元素的集合。
算法:算法是对特定问题的求解步骤的一种描述。
其中必须具备以下五个重要特性: 1 有穷性 2 确定性:阅读时不能用二义性,且相同的输入必须有相同的输出 3 可行性 4 输入:必须有 n个输入 5 输出:必须有输出。
且算法必须有穷,但是程序却不一定:如操作系统,只要不破坏系统,他就永远不会停止。即使没有作业要处理,他也会等待循环中。(克穹顶如初)

2:数据的逻辑结构和存储结构:
逻辑结构:数据与数据之间的逻辑关系,他与计算机的存储无关,是独立于计算机的。
物理结构(存储结构):是数据结构在计算机中的表示,他包括数据元素的机内表示和关系的机内表示。由于具体实现方法有顺序,链接,索引,散列等多种,所以一种数据结构
可以保存成多种物理结构。

3:算法的时间复杂度与空间复杂度如何分析:
时间复杂度:书p7;
算法分析,通常要考虑一下几个问题:即算法的指标:
1 正确性:不含语法错误;能够得到满足要求的结果;对于精心选择的,典型的,苛刻的数据也能得到满足要求的结果。
2 可读性:算法易于阅读和理解,以便于调试修改和补充。
3 健壮性:当数据输入非法时,算法也会进行处理,而不会产生莫名其妙的输出结果。
4 高效率:执行算法时间段,并且存储空间少。(却可见效)


算法所花费的时间与简单操作的次数有关。算法的执行时间为;简单操作的执行次数与简单操作的执行时间的乘积。而简单操作的执行时间与计算机的硬件环境有关,与算法无关,
所以操作时间与简单操作的次数成正比。n 表示次数,T(n)表示问题规模的函数。T(n)=O(f(n));即可以不用很精确,有了量级即可。
多项式的时间算法关系为:O(1) < O(logn) < O(n) < O(nlogn) < O(n2) < O(n3);
指数关系的时间算法关系为:O(2n) < O(n!) < O(nn);

算法的空间复杂度为:S(n)=O(g(n));


二:线性表
1:线性表的定义:
线性表是n个具有相同特性的数据元素的有限序列。其中n表示线性表的长度。n=0时为空表,n > 0时,通常把一个线性表记为(a1,a2,a3.....an).其中a1是第一个数据元素,
an是最后一个数据元素。除a1之外,表中任何一数据元素ai都有唯一前驱ai-1;除an之外,表中任何一个数据元素ai都有唯一后继ai+1;即存在一对一的关系,线性表的逻辑结构是
线性的。

2:线性表的基本操作及在顺序存储及链式存储上的实现。

线性表的基本操作:
a 初始化线性表
b 判断是否为空
c 求线性表的长度
d 读取线性表中第i个元素
...........下面总结两种物理存储的实现。

 

一:顺序表:


特点:逻辑上相邻的数据元素,他们的物理位置也是相邻的。
初始化:可以用java语言的一维数组来去实现。
求长度:public int saize(){
return len;
}
插入:public void add(Obj obj,int i){
//判断是否会溢出
if(len == maxlen){
System.out.println("会溢出");
}
//判断插入位置是否正确
if(i > n+1 || i < 1){
System.out.println("操作位置不正确");
}
//开始真正的核心算法
for(int j = (len-1); j >= i-1; j--){
a[j+1]=a[j];
}
a[i-1]=obj;
len++;

}
删除:public void del(int index){
//删除的核心算法
for(int j=index; j <= len-1; j++){
a[j]=a[j+1];
}
len--;

}
查找://break能够跳出循环
public void indexof(Object obj){
int i;
//核心算法
for(i=0; i < len; i++){
obj.equals(a[i]);
//break可以跳出循环
break;
}
if(i < len){
return i+1;
}else{
return 0;
}
}

二:链式表:

单链表:1 初始化链表(包括头插法还有尾差法)
2 求单链表长度
3 插入算法
4 删除算法
5 按值查找
6 取元素

1 初始化单链表:

头插法:
//定义指针,包括数据域还有指针域
class node{
//数据域
int data;
//指针域
node next;
}
class createnodes{

//头指针
node h;
//编写一个过渡指针,起到等量代换,中间过渡的作用
node p;

public void createnodes(){

h=new node();
h.next=null;
h.data=0;
//定义p(过渡)指针
p=h.next;
//写字
BufferedReader bur=new BufferedReader();
String str=null;
//核心算法
while((str= bur.readLine)!=null){
p=new node();
p.data=str;
p.next=h.next;
p=h.next;
}

}

}


尾差法:

//直接写核心算法,并且可以用上方的类
class createnodes{
//定义头指针
node h;
//作为过渡,起到等价代换之用
node t;
public void createnotes(){
h=new node();
h.next=null;
h.data=0;
t=h;

while((str=bur.readLine)!=null){
node p=new node();
p.next=null;
p.data=str;
t.next=p;
t=p;
}
}
}

2 求单链表长度
public void getsize(){
//先把p的指针放到最前端
p=h.next;
//用来计数
int i;
//然后遍历
//这个判断是关键---------------
while(p.next==null){
p=p.next;
i++;
}

}

3 插入算法
public void add(int index,Obj obj){
//插入主要是断链还有连接链
//首先判断index是不是符合标准
node p;
p=h.next;
for(int i=0; i < (index-1); i++){
count++;
if(i == (index-2)){
node s=new node();
s.data=obj;
s.next=p.next;
s=p.next;
break;
}
p=p.next;
}

}

4 删除算法
public void del(int index){
node p;
p=h.next;
//用来存储上一个的指针s.next=p.next;
static node s;
//判断index是否合法
for(int i=0; i<=(index-1); i++){
if(i==(index-2)){
s=p;
if(i==(index-1)){
s.next=p.next;
}
}
p=p.next;

}
}

5 按值查找
public void checkbyv(Obj obj){
node p=h.next;

while(p!=null){
p=p.next;
if(p.data.equals(obj)){
rerurn p.next;
}
}
}

 

3 各种变形链表(循环链表 双向链表 带头结点的链表)的表示和基本操作

1 循环链表的表示:
定义:循环链表是一种首尾相接的链表,如果最后一个点的指针不是null,而是头结点,那么此链表就是循环链表,即初始化单链表时,循环链表一定 h.next=h;

查找算法:
public void check(Obj obj){
//先初始化得到链p
while(p != h){
p=p.next;
if(p.data.equals(obj)){
return p;
}
}
}

2 双向链表的表示:
定义:链表的每个数据元素的指针域包括两个,一个是直接后继,另一个是直接前驱,则这样的链表称为双向链表。

3 双向循环链表:
定义:双向循环链表是将头指针的直接前驱指向了尾结点,将尾结点的直接后继指向了头结点。

//插入算法
重中之重:插入算法:插入算法要围绕着四个指针。
例:在a b 之间要插入c 那么
1 a 的后继
2 c 的前驱
3 c 的后继
4 b 的前驱

public void add(int index, Obj obj){
//核心算法
p=h.next;
static int count=0;
while(p!=h){
p=p.next;
count++;
if(count == (index-1)){
node s=new node();
//s的前驱
s.pir=p.pir;
//a的后继
p.pir.next=s;
//s的后继
s.next=p;
//p的前驱
p.pir=s;
break;

}
}

}
class node{
node pir;
int data;
node next;
}


//删除算法
重中之重:删除算法:删除算法要围绕两个指针
例:在a b c 之中删除b那么:
1 a 的后继
2 c 的前驱

public void del(int index){
//核心算法
p=h.next;
static int count;
while(p != h){
p=p.next;
count++;
if(count=(index-1)){
p.pir.next=p.next;
p.next.pir=p.pir;
break;
}
}


}


4 递归过程的特点及实现方法

1 递归特点:主要是设计,所以这个肯定以做题为主

 

5 栈和队列的基本概念,栈和队列的顺序存储,链式存储及其存储结构的特点。


1 栈:
定义:我们可以把一个死胡同当做一个栈,当汽车以a b c d e开进去时,出去的顺序却正好刚刚相反。由上例可知,栈是仅在一端进行删除和插入的线性表。其中,允许进入和
删除的那一端叫做栈顶,而不允许删除和插入的那一端叫做栈底。LIFO表,即last in first out .先入后出。

算法:1 入栈 push
2 出栈 pop
3 返回当前栈中元素个数 size
4 清空栈 clear
5 ........


2 栈的顺序存储:
(1)我们很容易想到用一个一维数组或类似的结构去存储它。
(2)它是利用一组地址连续的存储单元依次存储从栈顶到栈底的数据元素,通常用一维数组来表示。
(3)top作为栈顶去指示位置。但是他不是指针,只是一个整形变量。他表示栈顶在数组中的位置。

point:
(1)top=0表示这是一个空栈。-----------------------top=0为空,而不是像在数组里等于0代表1.----------------------------
(2)top=maxsiaze表示栈满,进栈之间要判断栈是否已满。

3 栈的一些算法:
1 初始化:public init(){
top=0;
}


2 判断栈空操作:public String isEmpty(){
if(top==0){
return "empty";
}else{
return "no empty";
}
}

3 进栈操作:public void push(Object obj){

if(top==maxsize){
System.out.println("满栈,不能进行进栈操作");
}else{
a[top]=obj;
top++;
}
}

4 出栈操作:public void pop(){
//直接上核心算法
int a=a[top-1];
top--;
return a;
}


5 求栈深:public int size(){
return top;
}

6 读取栈顶的操作:public int top(){
return a[top-1];
}


7 栈的链式存储:
定义:首先顺序栈有很大问题,由于不知道栈的长度是多少,所以有时候会浪费很多的空间,其次由于栈有可能过于小而装不下我们需要存储的东西,基于这两项缺陷,因而设计了
链栈。
我们把这种叫做链栈,与单链表类似,他的头部结点是栈顶,而尾部结点是栈底。其次他没有头结点,属于第一个元素就是栈顶,他的指针是next,而栈底的指针是null。


不是重点,他的算法就不详细说明了。只进行进栈与出栈。

进栈:
//和单链表类似的东西。
class stacknode{
int data;
stacknode next;
}
public void push(int x){
//完完全全的头插法,必须是这样。
stacknode s=new stacknode();
s.data=11;
s.next=top;
top=s;
}

出栈:
class stacknode{
int data;
stacknode next;
}
public void pop(){

}

应用:1 表达式求值
2 栈与函数的调用

 

2 队列:

1 定义:队列在生活上也是比比皆是。比如顾客的排队,再比如操作系统执行任务的顺序,都是采用队列的数据结构,即FIFO,(first in first out);
队列是仅允许在表的一端插入,另一端删除的线性表。允许插入的叫做对尾:rear.允许删除的一端叫做队头;front;

2 基本操作:(1) init();初始化
(2) queueempty();判断队列空。
(3)size();求队列的长度。
(4).........

3 队列的顺序存储结构:


定义:他由一个存放队列元素的一维数组构成。(一维数组)还有队头与队尾的指针组成。
队尾指针指示当前最后一个位置。而队头指针指示当前一维数组的头元素的前一个位置。

算法:
入队:sq.rear=sq.rear+1;
sq.elem[rear]=x;

出队:sq.front=sq.front+1;
return sq.elem[front];

重中之重:队列的top,rear 与数组的下标相同。


普通非循环队列:队列空的判断:sq.front=sq.rear;
而对于普通队列会出现假溢出的现象:有可能sq.rear=maxsize.而sq.front!=0.所以会出现这种情况。为此我们做了一个循环队列,来避免出现那样的问题。
循环队列:sq.elem[0]接在sq.elem[maxsize-1]之后,即收尾相接。
其实对于循环队列,与普通的队列在入队与出队方面相比,只有首和尾的不一样,其他都一样。即首尾加1之后都要除以总数并求得余数,否则会溢出。

重中之重:为了使循环队列不会因为无法判断而不知道什么时候是空,什么时候是满,则约定队头元素的指针元素(即队头元素的前一个元素的位置上不能存放元素,只能够存放指针),
因此,判断队列空与满的条件就出来了:
1 空:sq.rear=sq.front
2 满:(sq.rear+1)%maxsize=sq.front;


循环队列的算法:
1 初始化:public Circlequeue(){
front=0;
rear=0;
}

2 判断队空:public boolean isempty(){
if(sq.rear==sq.front){
return true;
}elseif((sq.rear+1)%maxsize==sq.front){
return false;
}else{
return false;
}

3 求长度:public int size(){
if(rear-front > 0){
return rear-front;
}else{
return front-rear;
}
}

4 读头元素的值:public int gethead(){
return elem[(front+1)%maxsize];
}

5 入队操作:public void push(int x){
//先判断是不是队满,满了则不能够再添加,
if((sq.rear+1)%maxsize==sq.front){
System.out.println("满了,不能够再添加");
}else{
sq.rear=(sq.rear+1)%maxsize;
elem[sq.rear]=x;
}
}

6 出队操作:public void pop(){
//先判断是不是队空,队空则无法出队
if(sq.rear==sq.front){
System.out.println("队空。没有元素可以出队");
}else{
x=elem[(sq.front+1)%maxsize];
sq.front=(sq.front+1)%maxsize;
}

}

 

4 队列的链式存储结构:
1 概念:也同样是为了避免溢出问题,所以队列在有顺序结构的同时,也有链式结构。
其中,表头需要头指针,表尾需要尾指针。
如果尾指针不存在的话,那么入队的时间复杂度就要由o(1)变为o(n)。


队列的应用;

 

3 特殊矩阵的压缩存储:
1 一般我们把要压缩存储的矩阵分为特殊矩阵还有稀疏矩阵。

特殊矩阵:对称矩阵,三角矩阵。
稀疏矩阵:用三元组顺序表,行逻辑链接的顺序表,十字链表。

 


三:广义表的基本概念和存储结构和基本操作。
这一章非常重要,但是先复习下面的,一会回过神再复习这个,即先从树开始。


四:树和二叉树。

1 树与森林的基本概念:

树:树与森林是差不多的,先来说树,树这个东西,和平常自然意义上的树正好结构相反。
这是一对多的关系。
通常类似的情景有家谱,公司的组织架构,或是电脑的磁盘。

森林:可以由树转化为森林,顾名思义,森林就是很多棵树,所以,我们可以使用最简单的方法来把树变为森林,就是把树的根节点去掉,如果是二叉树的情况且根是有左右子树的,那么去掉根节点,则会有两棵树,因而形成了森林。

树的定义:可以包括两方面的,一个是形式化定义,另一个是递归定义。

形式化定义: T={D,R}
D={1,2,3,4,5,6,,7.....} //D表示数据域。
R={<1,2>,<1,3>,<1,4>,<2,6>,<2,7>} //R表示前驱与后继关系。

递归定义:n = 0;空树。
n > 0;非空树。且每一个节点都是一棵树,而叶节点可以看做是只有根的树。

数据操作:一般分为三类:<1>:找双亲节点 <2>:插入删除 <3>:遍历树或是森林

其中遍历操作是最重要的,也是最要求算法的,其中遍历是要求算法的。在这里讲四种:
<A>:前序法 他所遵循的原则是中左右,所以最先访问的元素一定是根。
<B>:中序法 他所遵循的原则是左中右,所以最先访问的元素一定是最左的叶子节点
<C>:后序法 他所遵循的原则是左右中,所以最先访问的元素一定也是最左的叶子节点。
<D>:层序法 他所遵循的是按照层序,然后从左到右排序。

注:这里面一定要遵守的是先左后右,不管中间的在哪个位置。

树的基本术语:
<a> 节点的度:树中每个节点的子树的数量。
<b> 树的度:节点度中最大的那个。m次树,树的度为3,则为3次树。
<c> 分支节点:度不为0的节点。
<d> 叶子节点:度为0的节点。
<e> 层次:从根开始,1,2,3,4.。。。。。

树的性质:
<A>:树的节点数等于所有节点度数加一。 思路:虽然每个节点可以有多个后继,但是只能有一个前驱,即一个节点一个度。
<B>:度为m的树,第i层最多有m的(i-1)次方个节点。 思路:度为m,即最多节点的度为m,因此那这个算才是最大的。
<C>:高为h的m次树至多有m^h-1)(m-1)个节点。 思路:m^0+m^1+m^2+.....+m^(h-1)=(m^h-1)/(m-1).
<D>:n个节点的m次树的最小高度为logm(n(m-1)+1)。 思路:令n=(m^h-1)/(m-1)。

树的存储结构:
常见的有三种:(1)双亲存储结构:顺序存储结构。(2)孩子链式存储结构:链式存储结构 (3)孩子兄弟链存储结构:链式结构

(1)双亲存储结构:0 A -1(-1代表根节点)
1 B 0 (0是A的序号,即A是他的父节点)
2 C 0(0是A的序号,即A是他的父节点)
3 D 0(0是A的序号,即A是他的父节点)
4 E 2(2是C的序号,即C是他的父节点)
5 F 2(2是C的序号,即C是他的父节点)
双亲存储结构就时刻维护着这么一张顺序表。

(2)孩子链式存储结构:链表里包括两个域,一个是数据域,存放数据;另一个是指针域,指向自己的孩子节点;由于怕不能满足每个指针节点数量,则以树的度为指针域指针的数量。
这样造成的结果是指针域的浪费,因为多数节点的度没有那么多,而必须要声明那么多个指针域,因而造成空间的浪费。

(3)孩子兄弟链式结构:三个域:一个是值,一个指向孩子域,一个指向兄弟域。这样做特别节省空间。遍历的时候可以用递归的算法,值得研究一下。

 


4 二叉树:
直观定义:一个跟最多只能有两个分支,即可能是0个,1个,2个。
递归定义:<1>空;
<2>一个根节点和最多两个的互不相交的左子树与右子树;
二叉树与度为二的树的区别是:二叉树是区分左子树和右子树的,而树不区分,即没有顺序。

即他有五种情况:<A> 空 <B>只有一个根 <C>只有根与左子树 <D>只有根与右子树 <E>根与左子树与右子树


重中之重:性质:
<1> 非空二叉树叶节点等于双分支节点数加1. 即 n0=n2+1;
证明:
n:总节点数;n0:叶子节点数;n1:只有一个子节点的节点数; n2:有两个孩子节点的节点数;
式1: n=n0+n1+n2;

式2:总分支数=n1+2*n2;

由于除了根节点外每个节点只有一个前驱,而这里面的前驱就是分支数,也就是说总分指数就是所有节点数减去根节点数。
式3:总分支数=总结点数-1;

即 n0+n1+n2-1=n1+2*n2;
化简得:n0=n2+1;

<2> 非空二叉树第i层最多有2^(i-1)个节点。


<3> 高度为h的二叉树至多有m=(2^h-1)(2-1);即2^h-1;

满二叉树:
<A>所有的二叉树都有左孩子与右孩子节点。
<B>且叶子节点必须在最下面一层。
<C>有2^h-1个节点。

完全二叉树:
<A>只有最下面两层的节点可以小于二。
<B>并且最下面的叶节点必须从左到右依次排列。
<C>度为一的节点最多只能有一个,且必须为左孩子。

二叉树与树与森林之间的转换:
转换的必要性:<A>二叉树简单,一般问题都能用他解决。
<B>并且任何森林与树都能转化为二叉树。

<1>森林转化为二叉树:
1 相邻的兄弟节点之间要加一条水平线(每棵树的根节点可看做兄弟节点);
2 对于每个非叶节点k,除了其最左边的孩子节点外,删去k与其他孩子节点的连线;
3 所有的水平线段以左边节点为轴心,顺时针旋转45度;

重中之重:
做法:1 连线 2 拆线 3 旋转;
思路:把他的左孩子仍然作为其左节点;而其兄弟节点要称为右孩子节点。

 

<2>二叉树转换为森林:
1 对于每一个二叉树的任一节点k1,沿着其右子树方向寻找所有右孩子节点,直到最后一个节点没有了右孩子节点;记为k1,k2,k3...km;
2 删除k1 k2 k3 ...km之间的连线;
3 若k1有双亲节点,则连接k与ki(2<=i<=m);

 

 

 


5 二叉树的存储

分为两种:<A>顺序表
<B>链表


<A>顺序表:顺序表是一个数组,然后存储的时候是把二叉树排列成完全二叉树(注意不是满二叉树),把普通二叉树构成完全二叉树,如果二叉树有空的地方,
就把他补充为空,可以用#代表,然后用层序法去表示,按照1 2 3 4 5....去排列。
<B>链表:首先他更方便,其次他更加节省空间。因为如果是顺序表他需要补充空的地方,所以更加占据空间。
实现:就是 三个域: data leftchild rightchild

二叉树的算法实现:


一般二叉树算法包括: 创建(以后说)
查找(需要建立在遍历之上)
高度(需要用公式)
输出()

 

posted @ 2017-05-07 16:29  freebirds  阅读(156)  评论(0)    收藏  举报