面向对象方法描述算法
主要复习类相关的知识。
1、类的定义:类是一种用户定义的数据类型,一般格式:
class 类名{ private: 私有数据成员和成员函数; protected: 保护数据成员和成员函数; public: 公有数据成员和成员函数; };
2、类的成员函数
类的成员函数对类的数据成员进行操作,成员函数的定义体可以在类的声明体中,也可以在类的声明体外。
class Myclass{ int x; //数据成员 public: void setvalue(int x1) //成员函数 { x=x1; } void display(){ cout << "x=" << x << endl; } };
在类声明体中定义的函数都是内联函数。在类声明体外实现的函数可以通过在函数首部加上inline来表示该函数是内联的。如:
class Myclass{ int x; //数据成员 public: void setvalue(int x1); //成员函数 void display(); }; inline void Myclass::setvalue(int x1){ x=x1; } inline void Myclass::display(){ cout << "x=" << x << endl; }
在类的声明体内定义成员函数的优点是使整个类集中于程序代码的同一位置上,不利的方面是增加了类声明的规模和复杂性。
内联的函数代码并不被相同类的对象所共享,因而增大了程序的内存开销。
3、访问权限:公有成员(public)公开访问,私有成员(private)类本身的成员函数及友元类成员函数可访问,保护成员(protected)与私有成员类似但该类的派生类成员也可以访问。
class Sample1{ private: int i; protected: int j; public: int k; int geti() { return i; } //类的成员函数可以访问类的私有成员 int getj() { return j; } //类的成员函数可以访问类的保护成员 int getk() { return k; } //类的成员函数可以访问类的公有成员 };
定义该类的一个对象a:
Sample1 a;
其成员访问的合法性如下:
a.i; //非法,i为Sample1的私有成员 a.j; //非法,j为Sample1的保护成员 a.k; //合法,k为Sample1的公有成员
一般来说,公有成员是类的对外接口,而私有成员和保护成员是类的内部实现,不希望外界了解。
将类的成员划分为不同访问级别有两个好处:
- 信息隐蔽,即实现的封装,将类的内部实现和外部接口分开,这样使用该类的程序不需要了解类的详细实现;
- 数据保护,即将类的重要信息保护起来,以免其他程序不恰当地修改。
类对象
1、对象的定义格式:类名 对象名表;
2、对象成员的表示方法:对象名.成员名或者对象名.成员名(参数表)。对象指针名->成员名或者对象指针名->成员名(参数表)。
构造函数和析构函数
1、构造函数:它与类同名,并且没有返回值。C++在创建一个对象时,会自动调用该类的“构造函数”,在构造函数中可以执行初始化成员变量的操作。
class Sample{ public: Sample(参数表); }
2、重载构造函数:构造函数可以像普通函数一样被重载。C++根据说明中的参数个数和类型选择合适的构造函数。
#include <stdio.h> class Sample2 { int value; public: Sample2() { value=0; } //构造函数 Sample2(int v) { value=v; } //重载构造函数 int getvalue() { return value; } void setvalue(int v) { value=v; } }; void main() { Sample2 a[10]={0,1,2,3,4,5,6,7,8,9},b[10]; printf("输出a: "); for (int i=0;i<10;i++) { printf("a[%d]=%d ",i,a[i].getvalue()); if ((i+1)%5==0) printf(" "); } printf(" 输出b: "); for (i=0;i<10;i++) { printf("b[%d]=%d ",i,b[i].getvalue()); if ((i+1)%5==0) printf(" "); } printf(" "); }
3、析构函数:析构函数名称为符号“~”加类名。析构函数没有参数和返回值。一个类中只可能定义一个析构函数,所以析构函数不能重载。
class Sample{ public: ~Sample(); }
在析构函数中一般做一些清除工作,在C++中,清除就像初始化一样重要。通过析构函数来保证执行清除。对象超出其定义范围时(即释放该对象时),编译器自动调用析构函数。在以下情况下,析构函数也会被自动调用:如果一个对象被定义在一个函数体内,则当这个函数结束时,该对象的析构函数被自动调用。若一个对象是使用new运算符动态创建的,在使用delete运算符释放它时,delete将会自动调用析构函数。
#include <iostream.h> class Sample3 { int x,y; public: Sample3(int x1,int y1) //构造函数 { x=x1;y=y1; } ~Sample3() //析构函数 { cout << "调用析构函数." << endl; } void dispoint() { cout << "(" << x << "," << y << ")" << endl; } }; int main() { Sample3 a(12,6),*p=new Sample3(5,12); //对象指针指向创建的无名对象 cout << "First point=>"; a.dispoint(); cout << "Second point=>"; p->dispoint(); delete p; return 1; }
模板类:模板(template)用于把函数或类要处理的数据类型参数化,表现为参数的多态性。
模板用于表达逻辑结构相同,且具体数据元素类型不同的数据对象的通用行为,从而使得程序可以从逻辑功能上抽象,把被处理的对象(数据)类型作为参数传递。
#include <iostream> using namespace std; int max(int x,int y) { return x>y?x:y; } double max(double x,double y) { return x>y?x:y; } char max(char x,char y) { return x>y?x:y; } void main() { cout << max(1,3) << endl; cout << max(1.8,1.3) << endl; cout << max('c','e') << endl; } #include <iostream> using namespace std; template <typename T> T max(T x,T y) { return x>y?x:y; } void main() { cout << max(1,3) << endl; cout << max(1.8,1.3) << endl; cout << max('c','e') << endl; }
类模板使用户可以为类声明一种模式,使得类中的某些数据成员、成员函数的参数和返回值能取任意数据类型。
类模板用于实现类所需数据的类型参数化。类模板在表示数据结构如数组、二叉树和图等显得特别重要,这些数据结构的表示和算法不受所包含的元素类型的影响。
template 类型形参表 class 类模板名{ 类声明体; };
顺序表类模板
template <typename T> class SqList //顺序表类模板 { T *pelem; int length; public: SqList(int n) //构造函数,用于初始化单链表 { pelem=new T[n]; length=0; } ~SqList() //析构函数,用于释放分配的空间 { delete pelem; } bool ListEmpty() //判断线性表是否为空表 { return(length==0); } int ListLength() //求线性表的长度 { return(length); } int LoacteElem(T e) //按元素值查找 { int i=0; while (i<length && pelem[i]!=e) i++; if (i>=length) return 0; else return i+1; } bool ListInsert(int i,T e) //插入数据元素 { int j; if (i<1 || i>length+1) return false; i--; //将顺序表位序转化为pelem下标 for (j=length;j>i;j--) //将pelem[i]及后面元素后移一个位置 pelem[j]=pelem[j-1]; pelem[i]=e; length++; //顺序表长度增1 return true; } bool ListDelete(int i,T &e) //删除数据元素 { int j; if (i<1 || i>length) return false; i--; //将顺序表位序转化为pelem下标 e=pelem[i]; for (j=i;j<length-1;j++) pelem[j]=pelem[j+1]; length--; return true; } }; int main() { char e; int i; SqList<char> s(10); //定义一个大小为10的字符顺序表对象s s.ListInsert(1,'a'); s.ListInsert(2,'b'); s.ListInsert(3,'c'); s.ListInsert(4,'d'); s.DispList(); s.GetElem(2,e); cout << "第2个结点值:" << e << endl; i=s.LoacteElem('d'); cout << "数据值为d的结点序号为" << i << endl; cout << "删除第2个结点" << endl; s.ListDelete(2,e); s.DispList(); cout << "删除第3个结点" << endl; s.ListDelete(3,e); s.DispList(); cout << "插入e作为第1个结点" << endl; s.ListInsert(1,'e'); s.DispList(); cout << "插入f作为第3个结点" << endl; s.ListInsert(3,'f'); s.DispList(); return 1; }
template <typename T> struct NodeType //单链表结点的类型 { T data; //数据域 NodeType *next; //指针域 }; template <typename T> class LiStack //链栈类模板 { NodeType<T> *lhead; //单链表的头结点指针 public: LiStack() //构造函数,初始化栈 { lhead=new NodeType<T>(); lhead->next=NULL; } ~LiStack() //析构函数,销毁栈 { NodeType<T> *p=lhead->next; while (p!=NULL) { delete lhead; lhead=p; p=p->next; } delete lhead; //释放头结点空间 } int StackEmpty() //判断栈是否为空 { return(lhead->next==NULL); } void Push(T e) //进栈 { NodeType<T> *p; p=new NodeType<T>(); p->data=e; p->next=lhead->next; //插入p结点作为第一个数据结点 lhead->next=p; } bool Pop(T &e) //出栈 { NodeType<T> *p; if (lhead->next==NULL) //栈空的情况 return false; p=lhead->next; //p指向第一个数据结点 e=p->data; lhead->next=p->next; delete p; return true; } bool GetTop(T &e) //取栈顶元素 { if (lhead->next==NULL) //栈空的情况 return false; e=lhead->next->data; return true; } };
LiStack<int> st1; //整数链栈 LiStack<char> st2; //字符链栈 LiStack<double> st3; //实数链栈
使用STL设计数据结构算法
STL实现了数据结构课程中的大多数数据结构,STL实现的数据结构更高效,STL涵盖更多的数据结构设计算法时将数据结构作为工具,更高层次看待算法设计。
1、STL概述:STL主要由container(容器)、algorithm(算法)和iterator(迭代器)三大部分构成,容器用于存放数据对象(元素),算法用于操作容器中的数据对象。
一个STL容器就是一种数据结构,如链表、栈和队列等。这些数据结构在STL中都已经实现好了,在算法设计中可以直接使用它们。为此,使用STL时必须将下面的语句插入到源代码文件开头:
数据结构 |
说 明 |
实现头文件 |
向量(vector) |
连续存储元素。底层数据结构为数组,支持快速随机访问 |
<vector> |
双端队列(deque) |
连续存储的指向不同元素的指针所组成的数组。底层数据结构为一个中央控制器和多个缓冲区,支持首尾元素(中间不能)快速增删,也支持随机访问 |
<deque> |
链表(list) |
由结点组成的链表,每个结点包含着一个元素。底层数据结构为双向链表,支持结点的快速增删 |
<list> |
栈(stack) |
后进先出的序列。底层一般用deque(默认)或者list实现 |
<stack> |
队列(queue) |
先进先出的序列。底层一般用deque(默认)或者list实现 |
<queue> |
优先队列(priority_queue) |
元素的进出队顺序由某个谓词或者关系函数决定的一种队列。底层数据结构一般为vector(默认)或者deque |
<queue> |
集合(set)/多重集合(multiset) |
由结点组成的红黑树,每个结点都包含着一个元素,set中所有元素有序但不重复,multiset中所有关键字有序但不重复 |
<set> |
映射(map)/多重映射(multimap) |
由(关键字,值)对组成的集合,底层数据结构为红黑树,map中所有关键字有序但不重复,multimap中所有关键字有序但可以重复 |
<map> |
using namespace std;
这样直接把程序代码定位到std命名空间中。
#include <vector> using namespace std; void main() { vector<int> myv; //定义vector容器myv myv.push_back(1); myv.push_back(5); myv.push_back(3); myv.push_back(4); myv.push_back(2); … }
STL算法是用来操作容器中数据的模板函数,STL提供了大约100个实现算法的模版函数。例如,STL用sort()来对一个vector中的数据进行排序,用find()来搜索一个list中的对象。STL算法部分主要由头文件<algorithm>、<numeric>和<functional>组成。
#include <algorithm> using namespace std; void main() { int a[]={2,5,4,1,3}; sort(a,a+5); for (int i=0;i<5;i++) printf("%d ",a[i]); //输出: 1 2 3 4 5 printf(" "); }
STL迭代器用于访问容器中的数据对象。每个容器都有自己的迭代器,只有容器自己才知道如何访问自己的元素。迭代器像C/C++中的指针,算法通过迭代器来定位和操作容器中的元素。
iterator:指向容器中存放元素的迭代器,用于正向遍历容器中的元素。 const_iterator:指向容器中存放元素的常量迭代器,只能读取容器中的元素。 reverse_iterator:指向容器中存放元素的反向迭代器,用于反向遍历容器中的元素。 const_reverse_iterator:指向容器中存放元素的常量反向迭代器,只能读取容器中的元素。 ++:正向移动迭代器。 --:反向移动迭代器。 *:返回迭代器所指的元素值。
vector<int> myv; myv.push_back(1); myv.push_back(2); myv.push_back(3); vector<int>::iterator it; //定义正向迭代器it for (it=myv.begin();it!=myv.end();++it) //从头到尾遍历所有元素 printf("%d ",*it); //输出:1 2 3 printf(" "); vector<int>::reverse_iterator rit; //定义反向迭代器rit for (rit=myv.rbegin();rit!=myv.rend();++rit) //从尾到头遍历所有元素 printf("%d ",*rit); //输出:3 2 1 printf(" ");
2、常用的STL容器:顺序容器,适配器容器,关联容器。
vector(向量容器):它是一个向量类模板。向量容器相当于数组。用于存储具有相同数据类型的一组元素,可以从末尾快速的插入与删除元素,快速地随机访问元素。但是在序列中间插入、删除元素较慢,因为需要移动插入或删除处后面的所有元素。
vector<int> v1; //定义元素为int的向量v1 vector<int> v2(10); //指定向量v2的初始大小为10个int元素 vector<double> v3(10,1.23); //指定v3的10个初始元素的初值为1.23 vector<int> v4(a,a+5); //用数组a[0..4]共5个元素初始化v4 empty():判断当前向量容器是否为空。 size():返回当前向量容器的中的实际元素个数。 []:返回指定下标的元素。 push_back():在当前向量容器尾部添加了一个元素。 insert(pos,elem):在pos位置插入元素elem,即将元素elem插入到迭代器pos指定元素之前。 front():获取当前向量容器的第一个元素。 back():获取当前向量容器的最后一个元素。 erase():删除当前向量容器中某个迭代器或者迭代器区间指定的元素。 clear():删除当前向量容器中所有元素。
迭代器函数:begin()、end()、rbegin()、rend()。
#include <vector> using namespace std; void main() { vector<int> myv; //定义vector容器myv vector<int>::iterator it; //定义myv的正向迭代器it myv.push_back(1); //在myv末尾添加元素1 it=myv.begin(); //it迭代器指向开头元素1 myv.insert(it,2); //在it指向的元素之前插入元素2 myv.push_back(3); //在myv末尾添加元素3 myv.push_back(4); //在myv末尾添加元素4 it=myv.end(); //it迭代器指向尾元素4的后面 it--; //it迭代器指向尾元素4 myv.erase(it); //删除元素4 for (it=myv.begin();it!=myv.end();++it) printf("%d ",*it); printf(" "); }
deque(双端队列容器):它是一个双端队列类模板。双端队列容器由若干个块构成,每个块中元素地址是连续的,块之间的地址是不连续的,有一个特定的机制将这些块构成一个整体。可以从前面或后面快速插入与删除元素,并可以快速地随机访问元素,但删除元素较慢。
deque<int> dq1; //定义元素为int的双端队列dq1 deque<int> dq2(10); //指定dq2的初始大小为10个int元素 deque<double> dq3(10,1.23);//指定dq3的10个初始元素的初值为1.23 deque<int> dq4(dq2.begin(),dq2.end());//用dq2的所有元素初始化dq4 empty():判断双端队列容器是否为空队。 size():返回双端队列容器中元素个数。 push_front(elem):在队头插入元素elem。 push_back(elem):在队尾插入元素elem。 pop_front():删除队头一个元素。 pop_back():删除队尾一个元素。 erase():从双端队列容器中删除一个或几个元素。 clear():删除双端队列容器中所有元素。
迭代器函数:begin()、end()、rbegin()、rend()。
#include <deque> using namespace std; void disp(deque<int> &dq) //输出dq的所有元素 { deque<int>::iterator iter; //定义迭代器iter for (iter=dq.begin();iter!=dq.end();iter++) printf("%d ",*iter); printf(" "); } void main() { deque<int> dq; //建立一个双端队列dq dq.push_front(1); //队头插入1 dq.push_back(2); //队尾插入2 dq.push_front(3); //队头插入3 dq.push_back(4); //队尾插入4 printf("dq: "); disp(dq); dq.pop_front(); //删除队头元素 dq.pop_back(); //删除队尾元素 printf("dq: "); disp(dq); }
list(链表容器):它是一个双链表类模板。可以从任何地方快速插入与删除。它的每个结点之间通过指针链接,不能随机访问元素。
list<int> l1; //定义元素为int的链表l1 list<int> l2 (10); //指定链表l2的初始大小为10个int元素 list<double> l3 (10,1.23); //指定l3的10个初始元素的初值为1.23 list<int> l4(a,a+5); //用数组a[0..4]共5个元素初始化l4 empty():判断链表容器是否为空。 size():返回链表容器中实际元素个数。 push_back():在链表尾部插入元素。 pop_back():删除链表容器的最后一个元素。 remove():删除链表容器中所有指定值的元素。 remove_if(cmp):删除链表容器中满足条件的元素。 erase():从链表容器中删除一个或几个元素。 unique():删除链表容器中相邻的重复元素。 clear():删除链表容器中所有的元素。 insert(pos,elem):在pos位置插入元素elem,即将元素elem插入到迭代器pos指定元素之前。 insert(pos,n,elem):在pos位置插入n个元素elem。 insert(pos,pos1,pos2):在迭代器pos处插入[pos1,pos2)的元素。 reverse():反转链表。 sort():对链表容器中的元素排序。
迭代器函数:begin()、end()、rbegin()、rend()。 说明:STL提供的sort()排序算法主要用于支持随机访问的容器,而list容器不支持随机访问,为此,list容器提供了sort()采用函数用于元素排序。类似的还有unique()、reverse()、merge()等STL算法。
#include <list> using namespace std; void disp(list<int> &lst) //输出lst的所有元素 { list<int>::iterator it; for (it=lst.begin();it!=lst.end();it++) printf("%d ",*it); printf(" "); } void main() { list<int> lst; //定义list容器lst list<int>::iterator it,start,end; lst.push_back(5); //添加5个整数5,2,4,1,3 lst.push_back(2); lst.push_back(4); lst.push_back(1); lst.push_back(3); printf("初始lst: "); disp(lst); it=lst.begin(); //it指向首元素5 start=++lst.begin(); //start指向第2个元素2 end=--lst.end(); //end指向尾元素3 lst.insert(it,start,end); printf("执行lst.insert(it,start,end) "); printf("插入后lst: "); disp(lst); }
set(集合容器)/ multiset(多重集容器)set和multiset都是集合类模板,其元素值称为关键字。set中元素的关键字是唯一的,multiset中元素的关键字可以不唯一,而且默认情况下会对元素按关键字自动进行升序排列。set和multiset查找速度比较快,同时支持集合的交、差和并等一些集合上的运算,如果需要集合中的元素允许重复那么可以使用multiset。
empty():判断容器是否为空。 size():返回容器中实际元素个数。 insert():插入元素。 erase():从容器删除一个或几个元素。 clear():删除所有元素。 count(k):返回容器中关键字k出现的次数。 find(k):如果容器中存在关键字为k的元素,返回该元素的迭代器,否则返回end()值。 upper_bound():返回一个迭代器,指向关键字大于k的第一个元素。 lower_bound():返回一个迭代器,指向关键字不小于k的第一个元素。
迭代器函数:begin()、end()、rbegin()、rend()。
#include <set> using namespace std; void main() { set<int> s; //定义set容器s set<int>::iterator it; //定义set容器迭代器it s.insert(1); s.insert(3); s.insert(2); s.insert(4); s.insert(2); printf(" s: "); for (it=s.begin();it!=s.end();it++) printf("%d ",*it); printf(" "); multiset<int> ms; //定义multiset容器ms multiset<int>::iterator mit; //定义multiset容器迭代器mit ms.insert(1); ms.insert(3); ms.insert(2); ms.insert(4); ms.insert(2); printf("ms: "); for (mit=ms.begin();mit!=ms.end();mit++) printf("%d ",*mit); printf(" "); }
map(映射容器)/ multimap(多重映射容器)。map和multimap都是映射类模板。映射是实现关键字与值关系的存储结构,可以使用一个关键字key来访问相应的数据值value。在set/multiset中的key和value都是key类型,而key和value是一个pair类结构。pair类结构的声明形如:
struct pair{ T first; T second; } empty():判断容器是否为空。 size():返回容器中实际元素个数。 map[key]:返回关键字为key的元素的引用,如果不存在这样的关键字,则以key作为关键字插入一个元素(不适合multimap)。 insert(elem):插入一个元素elem并返回该元素的位置。 clear():删除所有元素。 find():在容器中查找元素。 count():容器中指定关键字的元素个数(map中只有1或者0)。
迭代器函数:begin()、end()、rbegin()、rend()。
map<char,int> mymap;//定义map容器mymap,其元素类型为pair<char,int> mymap['a'] = 1; //或者mymap.insert(pair<char,int>('a',1) );
获得map中一个值的最简单方法如下:
int ans = mymap['a'];
只有当map中有这个关键字('a')时才会成功,否则会自动插入一个元素,值为初始化值。可以使用find() 方法来发现一个关键字是否存在。
#include <map> using namespace std; void main() { map<char,int> mymap; //定义map容器mymap mymap.insert(pair<char,int>('a',1)); //插入方式1 mymap.insert(map<char,int>::value_type('b',2)); //插入方式2 mymap['c']=3; //插入方式3 map<char,int>::iterator it; for(it=mymap.begin();it!=mymap.end();it++) printf("[%c,%d] ",it->first,it->second); printf(" "); }
stack(栈容器)它是一个栈类模板,和数据结构中的栈一样,具有后进先出的特点。栈容器默认的底层容器是deque。也可以指定其他底层容器。例如,以下语句指定myst栈的底层容器为vector:
stack<string,vector<string> > myst;//第2个参数指定底层容器为vector empty():判断栈容器是否为空。 size():返回栈容器中实际元素个数。 push(elem):元素elem进栈。 top():返回栈顶元素。 pop():元素出栈。
注意:stack容器没有begin()/end()和rbegin()/rend()这样的用于迭代器的成员函数。
#include <stack> using namespace std; void main() { stack<int> st; st.push(1); st.push(2); st.push(3); printf("栈顶元素: %d ",st.top()); printf("出栈顺序: "); while (!st.empty()) //栈不空时出栈所有元素 { printf("%d ",st.top()); st.pop() ; } printf(" "); }
queue(队列容器)它是一个队列类模板,和数据结构中的队列一样,具有先进先出的特点。不允许顺序遍历,没有begin()/end()和rbegin()/rend()这样的用于迭代器的成员函数。
empty():判断队列容器是否为空。 size():返回队列容器中实际元素个数。 front():返回队头元素。 back():返回队尾元素。 push(elem):元素elem进队。 pop():元素出队。
#include <queue> using namespace std; void main() { queue<int> qu; qu.push(1); qu.push(2); qu.push(3); printf("队头元素: %d ",qu.front()); printf("队尾元素: %d ",qu.back()); printf("出队顺序: "); while (!qu.empty()) //出队所有元素 { printf("%d ",qu.front()); qu.pop(); } printf(" "); }
priority_queue(优先队列容器)它是一个优先队列类模板。优先队列是一种具有受限访问操作的存储结构,元素可以以任意顺序进入优先队列。一旦元素在优先队列容器中,出队操作将出队列最高优先级元素。
empty():判断优先队列容器是否为空。 size():返回优先队列容器中实际元素个数。 push(elem):元素elem进队。 top():获取队头元素。
pop():元素出队。
#include <queue> using namespace std; void main() { priority_queue<int> qu; //大根堆 qu.push(3); qu.push(1); qu.push(2); printf("队头元素: %d ",qu.top()); printf("出队顺序: "); while (!qu.empty()) //出队所有元素 { printf("%d ",qu.top()); qu.pop(); } printf(" "); }
参考:
【1】 李春葆,数据结构教程,清华大学出版社。