程序设计基础(上)
前言
C++的常见错误
语法错误:
混淆结构体类型与结构体变量的区别,为一个结构体类型赋值。不能为结构体类型赋值,只能为结构体变量的各成员赋值。 混淆类类型与对象的区别,为一个类类型赋值。不能为类类型赋值,只能为类类型的变量——对象的各成员赋值。
调用类的无参成员函数,不加小括号。函数调用的形式是:函数名(实参表),对无参函数调用时不能缺少小括号,即函数调用形式是:函数名()。
调用构造函数。类的构造函数在创建对象时,由编译系统自动调用一次。当一个对象已经产生,只能通过其它方法给对象的数据成员赋值,不能再次调用构造函数来修改对象的数据成员。
逻辑错误:
越界、溢出、等号与赋值混淆,分号,复合语句花括号,case+break,舍入误差
C++常见问题汇总
第一章 绪论
第二章 基本数据的表示与处理
利用后缀区分二进制(B),八进制(O/Q),十进制(D)和十六进制(H)
数制转换: 非十→十:按权展开求和
十→非十:整数部分除基取余,小数部分乘基取整
逗号表达式一次求值,最后一个表达式的值就是整个逗号表达式的值
正数的补码与原码相同,负数的补码等于其原码除符号位外按位取反之后末位加一;补码的补码等于原码。
(IEEE754)采用浮点数近似表示实数:,s为数值符号称为数符;M为数值精度称为尾数,采用原码表示,二进制规格化方法(最高有效位为1,只存储尾数的小数部分);指数E称为阶码,用移码表示
汉字编码外码(输入码)、交换码(国际码)、机内码和字形码(点阵/矢量)
逻辑表达式:短路求值
数据类型转换:隐式转换(自动转换)&显式转换(强制转换)
sizeof("x")的长度为2,存储字符串时自动加上了末尾的字符串结束标志'\0';但对于char定义的数组,大小为数组大小;
优先级和结合性:运算符优先级不是运算优先级,而是结合性优先级;结合性(左/右:单目/条件/赋值)
第三章 选择与迭代
单路,双路,嵌套,多路
if...else,switch...case...default
for,while,do...while
continue;break;goto
第四章 结构化数据
在C++中一般类型的数组访问必须逐个元素进行,但对字符串数组来说可以整体操作。
结构体:多属性数据
对结构体变量中成员的访问只能逐个进行(成员访问“.”),相同类型的结构体变量之间可以整体赋值
枚举:enum。只能用枚举常量为枚举变量赋值,而不能用整数的值
选择排序:
第五章 模块化
带默认形参值的函数:如果有函数声明,则应在函数声明处指定,否则直接在函数定义中指定。
函数重载:不同的函数具有相同的函数名,根据传递实参在数量或类型上的不同来判断
编译预处理命令以#开头,且末尾没有分号。包括文件包含,宏定义,条件编译;
文件包含:包含系统头文件
#include <文件名>
包含自定义头文件include "文件名"
宏定义:宏定义以
#define
开头,在编译预处理时利用指定的字符串进行宏替换,无参/有参条件编译:
#ifdef...[#else]...#endif
&#ifndef...[#else]...#endif
&#if..[#else]...#endif
例子#define DEBUG #ifdef DEBUG 程序块#endif
多文件系统避免重复包含,例global.h
#ifndef GLOBAL
#define GLOBAL
#include "global.h"
#endif
根据变量定义的位置和方式的不同,变量的存储类型分为全局变量,局部变量,静态全局变量和静态局部变量。作用域:作用范围(空间);生存期:寿命长短(时间)。全局变量:定义在所有函数之外,局部变量:在函数内部定义的变量。初值:在定义时若没有初始化。
类型 | 作用域 | 生存期 | 初值 | 备注信息 |
---|---|---|---|---|
全局变量 | 全局 | 全程 | 0 | |
静态全局变量 | 文件 | 全程 | 0 | static |
局部变量 | 定义它的函数 (或复合语句) | 与所在函数(或复合 语句执行期相同) | 随机 | |
静态局部变量 | 定义它的函数 (或复合语句) | 全程(之后不再重新生成和初始化, 自动使用上次生成的静态局部变量 并保留上次修改后的值) | 0 | static |
函数的作用域:外部函数(extern)/静态函数(static文件作用域)
第六章数据存储
内存空间:代码区、全局数据区、堆区(动态数据)、栈区(局部数据)。
&取地址↔*取内容:互逆运算
指针变量的数据类型必须与其指向变量的数据类型一致,否则需要给出显式的强制类型转换
数组名相当于指针常量
int a[4];
int *p1=a;//或int *p1=&a[0];
//对于一维数组,以下形式等价
//*(p1+i)(或*(a+i))可以写作p1[i](或a[i])
//p1+i(或a+i)也可以写作&p1[i](或&a[i])
二维数组的数组名是一个指向行的指针常量,指向行的指针变量int (*p)[列数]
利用new
和delete
进行内存空间的动态分配和释放堆内存分配成功后,返回动态分配的内存空间的首地址,保存在指针变量中,不成功返回NULL
动态分配用于存储多个数据元素的内存空间new <数据类型>[<表达式>]
,表达式可以是常量表达式也可以是变量表达式,用完之后需要显式释放delete []<指针表达式>
:指针表达式为首地址,若指向的堆空间只包含一个元素,可以省略[]
int *p,*pArray;
p=new int(3);//申请一个元素大小的内存空间,并初始化,值为3
pArray=new int[3];//申请三个int空间
delete p;//所指向堆内存只包含一个元素,省略[]
delete []pArray;
二级指针:指向指针的指针,定义格式:数据类型 **变量名
指针作为函数参数:
实参是一维数组的首地址,形参(
int p[]或者int *p
)是同类型的指针变量;实参是变量的首地址,对应的形参是与变量同类型的指针变量;
实参是二维数组的首地址:指向行的指针变量a.
<数据类型>(*<形参名>)[<行长度>]
b.<数据类型><形参名>[][行长度]
;实参是字符串常量的首地址,指针变量只能用于获取内存空间中的数据而不能修改;
-
实参是符号常量的首地址,对应形参为常量指针
const<数据类型>*<变量名>
不能使用常量指针修改其所指向内存空间中的数据,不能使用普通指针指向符号常量的首地址,可以通过指针常量修改内存中的数据,不能更改指针常量所指向的地址注意区分常量指针和指针常量:
const<数据类型>*<变量名>
&<数据类型>*const<常量名>
指针常量和符号常量,在定义时需要初始化
可以将一个指针同时定义为常量指针和指针常量,该指针指向的内存地址和内存中的数据均不可修改
实参是函数的首地址:函数名是一个函数指针常量,可以定义函数指针变量指向函数的首地址,并使用函数指针变量代替函数名进行调用;
<函数类型>(*<变量名>)(<形参类型表>)
,通过函数指针调用函数<函数指针>(<实参表>)
或(*<函数指针>)(<实参表>)
;函数指针变量可以作为形参,可以作为函数返回值(如果一个函数的返回值是指针,该函数被称为.指针函数)
引用:<数据类型> &<引用名>=<变量名>
,建立引用时必须用已经知道的变量名对其初始化,所引用的对象初始化后不可修改,对引用的操作与对所引用对象的操作效果完全一样。指针变量声明引用:<数据类型> *&<引用名>=<指针变量名>
。
引用主要在函数中:1. 函数的形参声明为引用;2. 函数的返回类型声明为引用
C++程序设计基础(下)
拓展学习
STL及使用示例
算法设计基本方法与策略基础
C++中的string类
排序算法
第一章 面向对象方法
对象,类,实例,消息,封装,继承,多态性,聚合和组合
类把对象的静态特征抽象成属性(attribute),对象的动态特征抽象成方法(method)
对象之间只能通过消息进行通信
类中的变量用来描述对象的状态(属性),这些变量被称为数据成员(或成员数据);类中的函数用来描述对象的方法(行为),这些函数被称为成员函数(或函数成员)
定义类的一般语法格式
class<自定义类类型名>
{
[public:]
[<公有成员说明表>]
[private:]
[<私有成员说明表>]
[protected:]
[<保护成员说明表>]
};
在C++中,对象的初始化工作是由成员函数——构造函数完成的,该函数在创建一个对象时被自动调用。构造函数可以重载,以满足对象多样性的初始化需要;
构造函数的特点:
构造函数名必须与类名相同
构造函数没有任何函数返回类型,void也不行
任意一个新的对象被创建时,编译系统都会自动调用构造函数,完成对该对象数据成员的初始化工作
如果在类定义时没有给出构造函数,系统会自动提供一个默认的无参构造函数:
<类名>(){}
定义对象的过程叫做类的实例化
一个对象创建以后,访问它的数据成员或调用它的成员函数,可以通过两种方式:a. 对象名+对象成员访问符“.”;b.对象指针+箭头成员访问运算符“->”。面向对象方法中的消息机制就是通过对象或指向对象的指针调用成员函数来实现的
C++通过三个关键字public(公有),private(私有)以及protected(保护)来指定类成员的访问控制,类成员的访问控制实现了类的封装性
公有成员:在程序的任何地方都可以被访问。一般将公有成员限制在成员函数上,使其作为类与外界的接口,程序通过这种函数来操作该类对象;
私有成员:只能被该类的成员函数或该类的友元函数访问。程序必须通过类的公有成员才能间接地访问类的私有成员,从而实现了对类成员的封装
保护成员:被声明为protected(保护)访问级别的数据成员或成员函数只能在该类的内部或其派生类类体中使用
如果没有指明访问级别,C++编译系统默认为私有成员(private)
析构函数:
析构函数名为:~<类名>
析构函数无任何函数返回类型说明
析构函数无参数,所以不能被重载
如果在类声明中没有给出析构函数,系统会自动给出一个默认的析构函数:
~<类名>(){}
当对象的生命周期结束或用delete释放动态对象时,系统自动调用析构函数完成对象撤销前的处理
析构函数的功能不仅仅局限于释放变量上,类这记者可以利用析构函数来执行最后一次使用类对象后所做的任何操作
拷贝构造函数的作用是用一个已经存在的对象来初始化一个正在创建的新对象。拷贝函数的特征有:
拷贝构造函数名与类名相同,形参只有一个,是对象的引用(不能重载拷贝构造函数)。拷贝构造函数的原形为
<类名>(<类名> &对象名)
拷贝构造函数无任何函数返回类型说明
如果在类声明中没有给出拷贝构造函数,系统会自动给出一个默认的拷贝构造函数,该拷贝构造函数只进行对象数据成员间的对位拷贝,即“浅拷贝”
在某些情况下,用户必须在类定义中给出一个显式的拷贝构造函数,以实现用户指定的用一个对象初始化另一个对象的功能,即“深拷贝”
在以下三种情况下,系统会自动调用拷贝构造函数:
当使用下面的声明语句用一个已存在的对象初始化一个新对象时:
<类名><新对象名>(<已存在对象名>)
或<类名><新对象名>=<已存在对象名>
对象作为实参,在函数调用开始进行实参和形参结合时
如果函数的返回值是类的对象,在函数调用完成返回时,系统自动调用拷贝构造函数,用return后面的已知对象来初始化一个临时新对象;
浅拷贝中两个指针指向同一个内存空间,析构时会造成内存泄漏,new 浅拷贝和深拷贝的区别?
类声明与类实现分离:
类声明:(.h)描述了类的结构,包括类的所有数据成员,函数成员和友元
类实现:(.cpp)定义了成员函数的具体功能
在类的实现文件中,成员函数的定义形式:
<函数类型><类名>::<函数名>(<形参数表>)
{
函数体
}
其中,“::”是作用域运算符,表示所定义的函数属于哪个类
类的静态成员的特点:
静态成员属于类,不属于任何对象
静态成员函数不能访问一般的数据成员,它只能访问静态数据成员,也只能调用其他的静态成员函数
无论对象是否存在,类的一个静态数据成员都只有一个,存于公用内存中,可被该类的所有对象共享
在创建对象时,会为对象的数据成员分配内存空间,但不会为该类的静态数据成员分配存储空间。所以,类设计者需要在类外对该类的静态数据成员进行定义,以获得内存空间。静态数据成员的定义形式:<类名><类名>::<静态数据成员名>[=<初值>]
a. 程序中,对静态数据成员的声明在类内进行,对一个静态数据成员的定义和初始化必须在类外进行,且只能出现一次
b. 静态数据成员定义时前面不要加关键字static
c. 在多文件结构中,静态数据成员定义和初始化最恰当的地方,是将它放在类的实现文件中
如果类的成员函数声明时被static修饰,他就是静态成员函数。静态成员函数是类中为外界提供的访问私有静态数据成员的接口。静态成员函数不能访问非静态数据成员
类的公有静态成员函数的访问形式:<类名>::<静态成员函数名>([实参])
或<对象名>.<静态成员函数名>([实参])
或<对象指针>-><静态成员函数名>([实参])
常量数据成员,const,在定义对象时通过构造函数的成员初始化列表获得初值(区别于符号常量在声明时赋初值),一旦对象被创建,常量数据成员的值不可修改
常量成员函数:有权读取对象的数据成员,无权修改对象数据成员的值。声明形式:<类型说明符><函数名>(<参数表>)const
(const放在函数声明的尾部,在类外定义时也要加上const)
this指针隐含于非静态成员函数,为类指针类型的形参,一般隐式使用,需要显式使用的情况:a. 非静态成员函数的形参名与对象的数据成员名相同时;b. 非静态成员函数返回的是对象本身或对象地址的时候
友元提供了一个一般函数与类的成员之间、不同类的成员之间进行数据共享的机制
友元函数:将普通函数声明为友元函数:
friend<数据类型><友元函数名>(参数表);
友元成员:将一个类的成员函数声明为另一个类的友元函数:
friend<类型><含有友元成员的类名>::<友元成员名>(参数表);
友类:将一个类声明为另一个类的友类:
friend <友类名>;
或friend class <友类名>;
友元不可传递,类的友员函数不是本类的成员函数,访问本类的成员时等同于一般的外部函数,使用对象名来访问。
类的对象成员:
一个类的对象是另一个类的成员;
一个类的成员函数是另一个类的友元;
一个类定义在另一个类的说明中,即类嵌套;
一个类作为另一个类的派生类。
对象成员的声明:类名<对象成员名表>
;初始化调用构造函数完成:<对象成员1>(<初值表>)[,...,<对象成员n>(<初值表>)]
自定义类的运算符重载:
除了类属运算符“.”,成员指针运算符“.*”,作用域运算符“::”,sizeof运算符和三目运算符“?:”5中运算符外,其他的运算符都可以重载
运算符重载限制在C++语言中已有的运算符范围内的允许重载的运算符之中,不能创建新的运算符
运算符重载实质上是函数重载,编译程序对运算符重载的选择遵循函数重载的选择原则
重载之后的运算符不能改变运算符的优先级和结合性,也不能改变运算符操作数的个数及语法结构
运算符重载是针对新类型数据的实际需要对原有运算符进行的适当的改造,重载的功能应当与原有功能相类似
对于自定义类的运算符重载函数,大部分运算符可以将其定义为类的成员函数,也可以将其定义为类的非成员函数,而非成员函数一般采用友元函数形式
成员函数形式的运算符函数定义的一般形式:(参数-1,隐含this)
<返回类型说明符>operator<运算符符号>(<参数表>)
{
<函数体>
}
类友元形式的运算符重载:类非成员函数形式的运算符重载函数一般采用友元函数,运算符重载为类的友元函数,需要在类内进行声明,形式:friend<函数类型>operator<运算符>(<参数表>);
,无隐含this,所有操作数用过函数的形参进行传递,函数的参数与操作数自左至右一一对应
重载为类的成员函数or友元函数规则:
单目运算符→类的成员函数;双目运算符-→类的友元函数
只能重载为类的成员函数的双目运算符:
=
、()
、[]
、->
若一个运算符的操作需要修改对象的姿态,重载为成员函数
若运算符所需的操作数(尤其是第一个操作数)希望有隐式类型转换,只能选用友元函数
的运算符函数是一个成员函数时,最左边的操作数必须是运算符类的一个类对象(或对该类对象的引用)。如果左边的操作数必须是一个不同类的对象,或者是一个内部类型的对象,改运算符函数只能作为一个友元函数来实现
当需要重载运算符具有可交换性时,选择重载为友元函数
第二章 继承与多态
派生类(子类)&基类(父类)
派生类会继承基类中定义的所有属性和方法,派生类也能够在派生类中定义派生类特有的属性和方法
定义派生类的语法:
class 派生类名:继承方式 基类名
{
派生类成员声明;
};
继承方式包括:public(公有继承),protected(保护继承),private(私有继承)
派生类不能直接访问从基类继承下来的私有成员
访问控制方式 | 含义 |
---|---|
public | 类的公有成员,在任何地方都可以直接访问 |
private | 类的私有成员,只在该类的成员函数中可以直接访问,在其他地方均不能直接访问 |
protected | 类的保护成员,在该类及其派生类的成员函数中可以直接访问,在其他地方不能直接访问 |
在定义派生类时,可以指定的继承方式为public(公有继承),protected(保护继承),private(私有继承,缺省方式)
继承方式\访问控制方式 | public | private | protected |
---|---|---|---|
public | public | 不可直接访问 | protected |
private | private | 不可直接访问 | private |
protected | protected | 不可直接访问 | protected |
派生类中函数重定义
派生类构造函数的作用:主要是对派生类中新添加的数据成员做初始化工作;在创建派生类对象,执行派生类构造函数时,系统会自动调用基类的构造函数来对基类中定义的数据成员进行初始化
派生类析构函数的作用:主要是清除派生类中新添加的数据成员,释放它们所占据的系统资源;在销毁派生类对象、执行派生类析构函数是,系统会自动调用积累的自构函数来释放基类中数据成员所占据的系统资源
派生类中构造函数的定义:派生类名(形参列表):基类名(实参列表){ 派生类中数据成员的初始化}
;无参:派生类名(形参列表){ 派生类中数据成员的初始化}
等价于派生类名(形参列表):Person(){ 派生类中数据成员的初始化}
派生类中析构函数的定义形式与基类完全相同
C++基类中,不能被派生类继承的有构造函数和析构函数。
多继承:
class 派生类名:继承方式 基类名1,继承方式 基类名2,...,继承方式 基类名n,
{
派生类成员声明;
};
按继承顺序构造,析构函数的调用顺序总是与构造函数的调用顺序相反
二义性:在定义派生类时,可以通过虚拟继承方式将基类声明为虚基类。虚基类中的成员在类的继承关系中只会被继承一次,从而解决多重继承中的二义性问题。virtual,最后需要显式调用虚基类的构造函数进行初始化
类型兼容是多态性的前提,指的是在基类对象可以出现的任何地方,都可以用公有派生类的对象来替代:1. 可以用派生类对象为基类对象赋值;2. 可以用派生类对象初始化基类引用;3. 可以用派生类对象地址为基类指针赋值
用派生类对象替代基类对象进行赋值操作后,通过基类对象、基类对象引用和基类指针只能访问派生类从基类继承的成员
通过类型兼容,对于基类及其公有派生类的对象,可以使用相同的函数统一进行处理。这种能够根据指针或引用所表示的对象的实际类型来调用该对象所属类的函数,而不是每次都调用基类中函数的特性,就是多态性
先期绑定(静态绑定)&后期绑定(动态绑定)
C++通过虚函数实现“动态绑定”计数。虚函数的声明方法是在基类的函数声明前或函数定义的函数头前(无函数声明时)加上virtual关键字,虚函数可以被继承
只有使用基类的指针或引用调用虚函数时才能实现多态性。如果使用对象调用虚函数,则不具有多态性,必然是调用该对象所属类的成员函数
抽象类:不能实例化,唯一用途是为其他类提供合适的基类
纯虚函数是在声明时初始化为0的虚函数,一个类如果是抽象类,则该类至少有一个成员函数是纯虚函数,定义:virtual <函数类型>纯虚函数名(<形参类型表>)=0
第三章 输入输出流
in.getline(字符数组名或字符型指针,字符个数n,终止标识符)
“EOF”(end of file)文件结束符对应于键盘组合键“Ctrl+Z”
打开文件使用文件流类的成员函数open(),函数原型为void open(const char* filename, int mode);
方式 | 作用 |
---|---|
ios::in | 以输入方式打开文件,对文件进行读操作,该文件必须存在(BOF) |
ios::out | 以输出方式打开文件,对文件进行写操作(BOF) |
ios::app | 以追加方式打开文件,所有输出附加在文件末尾(EOF) |
ios::ate | 打开文件时,文件指针定位到文件尾(EOF) |
ios::binary | 以二进制方式打开文件,缺省的方式是文本方式(BOF) |
ios::trunc | 如果文件已存在,则先删除文件内容(BOF) |
可以用位或运算符“|”连接以上属性
一般将纯文本内容存储在文本文件中,将数值型数据或含有数值结构体数据存储在二进制文件中
is_open()检查文件是否打开成功,返回一个bool,为真表示打开成功,为假表示打开失败,close()关闭
输出流类提供了用于输出一个数据块的成员函数write(),输出流对象可以调用这个函数实现向输出设备输出指定个数的多个字符的操作,其有两个参数,调用格式为:out.write(字符型指针,字节数a)
相对应的有read(),read()能够读取空白字符,读取指定个数字符而不是字符串,最后不会自动加字符串结束符'\0'
文件的随机读写seekg()&seekp()
istream&seekg(long offset,seek_dir origin=ios::beg);
ostream&seekp(long offset,seek_dir origin=ios::beg);
函数的功能是文件指针从参照位置origin开始移动offset个字节,取值为正表示向后(文件尾部)移动文件指针,seek_dir为枚举类型,有ios::beg:文件首;ios::cur:文件指针当前位置;ios::end:文件尾三个枚举常量
第四章 模板
模板就是参数化的函数或类,面板式将数据类型作为参数,根据数据类型参数产生函数和类的机制,分为函数模板和类模板,模板是泛化编程的主要方法之一
模板关键字:template;模板参数:typename
实例化的函数模板称为模板函数,函数模板的实例化是在函数调用的时候由编译器完成的
函数重载:可执行不同代码;函数模板:相同代码,处理数据类型不同
当函数模板与一般函数同名时,遵循:
一个函数调用首先寻找参数完全匹配一致的一般函数,如果找到就调用它
寻找一个函数模板,使其实例化,生成一个匹配的模板函数,然后调用该模板函数
类模板的实例化需要在程序中显式地指定
第五章 概论
数据结构中的数据项是数据的不可分割的最小单位。
数据的逻辑结构&数据的存储结构(物理结构)&数据结构的操作及实现
通常把数据元素之间的股有关系用前驱和后继来描述
二元关系/图形表示
逻辑结构:
线性结构:1. 有且仅有一个没有前驱的节点,通常将该节点称为根节点;2. 除了根节点没有前驱,最后一个节点没有后继外,其他每一个节点都有一个前驱和一个后继;3. 线性结构在插入或删除任何一个节点后还是线性结构
非线性结构:一个节点可能有多个前驱和后继:1. 树状结构:一对多 2. 网状结构:关系任意;3. 集合结构:没有关系
顺序存储结构(紧凑存储结构);链式存储结构(频繁插入删除);索引存储结构(索引表);散列存储结构
基本操作:创建/清除/插入/删除/访问/更新/查找/排序
第六章 线性表
抽象数据类型(Abstract Data Type,ADT)
线性表的顺序存储结构(顺序表)特点:1. 线性表中所有元素所占的存储空间是连续的;2. 线性表的逻辑顺序与物理顺序一致;3. 数组中的每一个元素的位置可以用公式来确定
类模板的声明和实现要放在同一个头文件中
链式存储结构:存储节点=数据域+指针域
头文件:便于代码复用
空表:循环列表head->next==head
;单向链表:head->next==NULL
第七章 栈和队列
栈(Stack)和队列(Queue):操作受限的线性表
栈:插入和操作都只能在标的同一端,栈顶(表尾):Top;栈底(表头):Bottom。当栈中没有元素时称为空栈;插入元素——进栈(入栈):push;删除元素——退栈(出栈):pop。先进后出(FILO)&后进先出(LIFO)下推表。上溢(Overflow)&下溢(Underflow)。顺序栈&链接栈
队列:入队&出队;队尾(rear)&队头(front);先进先出(FIFO)&后进后出(LILO)
顺序队列:头指针指向队头元素,尾指针指向队尾元素的下一个位置
循环队列:避免假上溢front=(front+1)%(MaxSize)
&rear=(rear+1)%(MaxSize)
判断“空”或“满”:1. 少用一个元素空间(rear+1)%(MaxSize)==front
为满,rear时钟指向空闲元素空间;2. 采用计数器记录队列实际长度,size==0为空。
第八章 树和二叉树
树是由n(n≥0)个结点组成的有限集T,当n=0时,称为空树;当n>0时,满足:1. 有且仅有一个没有前驱的结点,该节点称为树的根节点;2. 将根节点去除后,其余结点可分为m(m≥0)个互不相交的自己T1、T2、...、Tm,其中每个子集Ti(i=1,2,...,m又是一棵树,并称其为根的子树)。
表示方法:树型图、嵌套集合表示法、凹入表表示法、广义表表示法
一个结点后继的数目称为该结点的度,树中各结点的最大值称为树的度;树中各结点的层的最大值称为树的深度;从一个结点到其后继结点之间的连线称为一个分支,从一个结点X到另一个结点Y所经历的所有分支构成结点X到结点Y的路径,一条路径上的分支数目称为路径长度,从树的根结点到其他各个结点的路径长度之和称为树的路径长度;树中度为0的结点称为叶子结点(或终端结点),度不为0的结点称为分支结点(或非终端结点),除根结点以外的分支结点也成为内部结点;在树中,一个结点的后继结点称为该结点的孩子,相应地,一个结点的前驱结点称为该结点的双亲;同一双亲的孩子结点之前互称为兄弟,不同双亲但在同一层的结点之前互称为堂兄弟;从树的根结点到某一个结点X的路径上经历的所有结点(包括根节点但不包括结点X)称为结点X的祖先,以某一结点X为根的子树上的所有非根节点(即除结点X外)称为结点X的子孙;对于树中任一结点,如果其各棵子树的相对次序被用来表示数据之间的关系,即交换子树位置会改变树所表示的内容,则称该树为有序树,否则称为无序树;m(m≥0)棵互不相交的树的集合就构成了森林;
二叉树是每个结点的度小于等于2的有序树
顺序编号法:自上而下自左至右
满二叉树是指除了最后一层的结点为叶子结点外其他结点都有左、右两棵子树的二叉树;完全二叉树是指其结点与相同深度的满二叉树中的结点编号完全一致的二叉树;对于深度为k的完全二叉树,其前k-1层与深度为k的满二叉树的前k-1层完全一致,只是在第k层上有可能缺少右边若干个结点。满二叉树必然是完全二叉树,而完全二叉树不一定是满二叉树。
二叉树的基本性质:
在二叉树的第i层上至多有个结点
深度为k的二叉树至多有个结点
-
在二叉树中,若度为0的结点(即叶子结点)数为,度为2的结点数为,则
- 结点总数:入;出:
具有n个结点的完全二叉树其深度为(其中表示不大于的最大整数)
-
对于采用顺序编号的完全二叉树
若一个分支节点的编号为i,则其左子树的根节点(即左孩子结点)编号为2i,右子树的根节点(即有孩子结点)编号为2i+1
若一个非根节点的编号为i,则其双亲节点的编号为(其中表示不大于i/2的最大整数)
二叉树的顺序表示适用于完全二叉树而不适用于非完全二叉树(空间利用效率较低)
二叉树的链式表示:二叉链表表示方法leftchild+data+rightchild
;三叉链表表示leftchild+data+parent+rightchild
二叉树的遍历,就是按照某种规则依次访问二叉树中的每个结点,且每个结点仅被访问一次。根据节点访问顺序上的不同,有四种常用的遍历方式:先序遍历、中序遍历、后序遍历和逐层遍历
在先序中序和后序遍历时均先访问左子树后访问右子树,遍历方式以根的先后为准
二叉树的中序遍历二叉链表的实现既可采用非递归方式,也可采用递归方式。 根据二叉树的先序遍历序列可以确定二叉树的根结点,根据二叉树的中序遍历序列并不能确定二叉树的根结点,已知一棵二叉树的先序遍历序列和中序遍历序列可以唯一地构造出该二叉树。
二叉排序树,又称二叉查找树,它或者是一棵空树,或者是具有如下性质的二叉树:
若它的左子树非空,则左子树上所有结点的值均小于根结点的值
若它的右子树非空,则右子树上所有结点的值均大于根结点的值
左、右子树也分别是二叉排序树
其中序遍历有序
平衡二叉查找树
哈夫曼树(最优二叉树):具有最短带权路径长度
第九章 图
顶点V(G)和E(G)
有向图:有向边<弧>&无向图:无向边(边)
无向图中与顶点相关联的边的数目称为顶点的度;有向图中顶点的入度和出度之和为有向图的度
路径,一条路径中边的数目称为路径长度在一条路径中,若一个顶点至多只经过一次,则该路径称为简单路径;若组成路径的顶点序列中第一个顶点与最后一个顶点相同,则该路径称为回路(或环),简单回路/简单环;若任意两个顶点都是连通的:连通图,单向连通/强连通,子图/连通分量/强连通分量极大连通子图,极大指向连通子图或强连通子图中再添加一个顶点,该子图就不再连通或强连通;权/带权图;生成树/最小生成树:只有连通图才有生成树,边上的权最小的生成树称为最小生成树
图的表示:邻接矩阵、邻接压缩表和邻接链表
广度优先算法:逐层遍历;深度优先算法:先序遍历