【数据结构与算法学习笔记】PART2 向量(接口与实现,可扩充向量,无序向量,有序向量)
来源:互联网 发布:拓扑算法 编辑:程序博客网 时间:2024/05/20 07:34
Vector 向量 最基本的线性结构—线性序列
- (a)接口与实现
根据统一的接口规范定制一个统一的数据结构,如何通过更加有序的算法使得对外的接口能够更加高效的工作
抽象数据类型(ADT,Abstract data type):数据模型+定义在改模型上的一组操作
抽象定义外部的逻辑特性操作&予以
数据结构:基于某种特定语言,实现ADT的一整套算法
ADT
Application ——> Interface——>Implementation
从数组到向量:
C/C++语言中,数组A[]中的元素与[0,n)内的编号一一对应
向量结构必须提供的接口:
http://dsa.cs.tsinghua.edu.cn/~deng/ds/src_link/vector/vector.h.htmVector模板类:
typedef int Rank; //秩 #define DEFAULT_CAPACITY 3 //默认的初始容量(实际应用中可设置为更大) template <typename T> class Vector { //向量模板类 protected: Rank _size; int _capacity; T* _elem; //规模、容量、数据区 void copyFrom ( T const* A, Rank lo, Rank hi ); //复制数组区间A[lo, hi) void expand(); //空间不足时扩容 void shrink(); //装填因子过小时压缩 bool bubble ( Rank lo, Rank hi ); //扫描交换 void bubbleSort ( Rank lo, Rank hi ); //起泡排序算法 Rank max ( Rank lo, Rank hi ); //选取最大元素 void selectionSort ( Rank lo, Rank hi ); //选择排序算法 void merge ( Rank lo, Rank mi, Rank hi ); //归并算法 void mergeSort ( Rank lo, Rank hi ); //归并排序算法 Rank partition ( Rank lo, Rank hi ); //轴点构造算法 void quickSort ( Rank lo, Rank hi ); //快速排序算法 void heapSort ( Rank lo, Rank hi ); //堆排序(稍后结合完全堆讲解) public: // 构造函数 Vector ( int c = DEFAULT_CAPACITY, int s = 0, T v = 0 ) //容量为c、规模为s、所有元素初始为v { _elem = new T[_capacity = c]; for ( _size = 0; _size < s; _elem[_size++] = v ); } //s<=c Vector ( T const* A, Rank n ) { copyFrom ( A, 0, n ); } //数组整体复制 Vector ( T const* A, Rank lo, Rank hi ) { copyFrom ( A, lo, hi ); } //区间 Vector ( Vector<T> const& V ) { copyFrom ( V._elem, 0, V._size ); } //向量整体复制 Vector ( Vector<T> const& V, Rank lo, Rank hi ) { copyFrom ( V._elem, lo, hi ); } //区间 // 析构函数 ~Vector() { delete [] _elem; } //释放内部空间 // 只读访问接口 Rank size() const { return _size; } //规模 bool empty() const { return !_size; } //判空 int disordered() const; //判断向量是否已排序 Rank find ( T const& e ) const { return find ( e, 0, _size ); } //无序向量整体查找 Rank find ( T const& e, Rank lo, Rank hi ) const; //无序向量区间查找 Rank search ( T const& e ) const //有序向量整体查找 { return ( 0 >= _size ) ? -1 : search ( e, 0, _size ); } Rank search ( T const& e, Rank lo, Rank hi ) const; //有序向量区间查找 // 可写访问接口 T& operator[] ( Rank r ) const; //重载下标操作符,可以类似于数组形式引用各元素 Vector<T> & operator= ( Vector<T> const& ); //重载赋值操作符,以便直接克隆向量 T remove ( Rank r ); //删除秩为r的元素 int remove ( Rank lo, Rank hi ); //删除秩在区间[lo, hi)之内的元素 Rank insert ( Rank r, T const& e ); //插入元素 Rank insert ( T const& e ) { return insert ( _size, e ); } //默认作为末元素插入 void sort ( Rank lo, Rank hi ); //对[lo, hi)排序 void sort() { sort ( 0, _size ); } //整体排序 void unsort ( Rank lo, Rank hi ); //对[lo, hi)置乱 void unsort() { unsort ( 0, _size ); } //整体置乱 int deduplicate(); //无序去重 int uniquify(); //有序去重 // 遍历 void traverse ( void (* ) ( T& ) ); //遍历(使用函数指针,只读或局部性修改) template <typename VST> void traverse ( VST& ); //遍历(使用函数对象,可全局性修改) }; //Vector
- (b)可扩充向量
不足:有可能出现上溢,有可能出现下溢(装填因子<<50%,λ= _size/_capacity)
动态空间管理:起点
在即将发生上溢时,适当地扩大内部数组的容量
为何必须采用“容量加倍”策略?其它策略不行吗(容量递增)? 不大行!
容量递增:算术级数
容量加倍:几何级数
平均分析VS分摊分析
平均复杂度或期望复杂度:
根据数据结构各种操作出现概率的分布,兼顾敌营的成本加权平均,各种可能的操作,作为独立事件分别考察,割裂了操作之间的相关性和连贯性,往往不能准确地评判数据结构和算法的真实性能。
分摊复杂度:
对数据结构连续的实施足够多次操作,所需总体成本分摊至单次操作,从实际可行的角度,对一系列操作做整体的考量,更加忠实的刻画了可能出现的操作序列,可以更为精确地评判数据结构和算法的真实性能
- (c)无序向量:向量的最基本形式 ——如何定义和实现相应的操作接口
Template <typename T> Vector { /**具体实现**/} ; //使用的时候可以灵活指定类型
Vector < int > myVector;
Vector < float > myVector;
Vector < char > myVector;
Vector < BinTree > forest;
向量元素的访问 通过get 和put接口 ,已然可以读写向量元素,但就便捷性而言,远不如数组元素的访问方式
可以采用数组的访问方式,为此,需要重载下标操作符[]
template <typename T> //0<r<=size;
T & Vector <T>::operator[](Rank r) const {return _elem[r];}
此后,对外的v[r]即对应于内部的v._elem[r]
可以作为右值,也可以作为左值(得益于对其的访问方式是引用“&”) 循秩访问方式
向量的插入算法:
区间删除:
单元素删除:
向量的查找算法:
向量的唯一化算法:
向量的遍历操作:
遍历向量,统一对个元素分别实施visit操作
- (d1)有序向量:唯一化
template <typename T> int Vector<T>::disordered() const { //返回向量中逆序相邻元素对的总数 int n = 0; //计数器 for ( int i = 1; i < _size; i++ ) //逐一检查_size - 1对相邻元素 if ( _elem[i - 1] > _elem[i] ) n++; //逆序则计数 return n; //向量有序当且仅当n = 0 }
template <typename T> int Vector<T>::uniquify() { //有序向量重复元素剔除算法(低效版) int oldSize = _size; int i = 1; //当前比对元素的秩,起始于首元素 while ( i < _size ) //从前向后,逐一比对各对相邻元素 _elem[i - 1] == _elem[i] ? remove ( i ) : i++; //若雷同,则删除后者;否则,转至后一元素 return oldSize - _size; //向量规模变化量,即被删除元素总数 }复杂度:运行时间主要取决于while循环,最坏的情况下,每次都需要调用remove ,累计O(n^2)
template <typename T> int Vector<T>::uniquify() { //有序向量重复元素剔除算法(高效版) Rank i = 0, j = 0; //各对互异“相邻”元素的秩 while ( ++j < _size ) //逐一扫描,直至末元素 if ( _elem[i] != _elem[j] ) //跳过雷同者 _elem[++i] = _elem[j]; //发现不同元素时,向前移至紧邻于前者右侧 _size = ++i; shrink(); //直接截除尾部多余元素 return j - i; //向量规模变化量,即被删除元素总数共计n-1次迭代,每次常数时间,累计O(n)时间
</pre>忙了一个周之后又放了两个周的暑假,啥也没学,罪过罪过<p></p><p></p><p>有序向量的查找算法:</p><p></p><p>统一接口</p><p></p><pre name="code" class="cpp">template <typename T> //在有序向量的区间[lo, hi)内,确定不大于e的最后一个节点的秩 Rank Vector<T>::search ( T const& e, Rank lo, Rank hi ) const { //assert: 0 <= lo < hi <= _size return ( rand() % 2 ) ? //按各50%的概率随机使用二分查找或Fibonacci查找 binSearch ( _elem, e, lo, hi ) : fibSearch ( _elem, e, lo, hi ); }
语义约定
至少应该便于有序向量自身的维护 V.insert (1+V.search(e),e)
即便失败,也应该给出新元素适当的插入位置
若允许重复元素,则每一组也需按其插入的次序排列
约定:在有序向量区间中,确定不大于e的最后一个元素,如果e小于区间内任意数,则返回lo-1(左侧哨兵),如果大于,则返回hi-1(末元素 = 右侧哨兵左邻)
版本A:
原理
减而治之:以任意元素 x=s[mi]为界,都可将待查找区间分为三部分
这个地方就是那种最简单的二分查找的算法,不写了。
</pre><pre name="code" class="cpp">// 二分查找算法(版本A):在有序向量的区间[lo, hi)内查找元素e,0 <= lo <= hi <= _size template <typename T> static Rank binSearch ( T* A, T const& e, Rank lo, Rank hi ) { while ( lo < hi ) { //每步迭代可能要做两次比较判断,有三个分支 Rank mi = ( lo + hi ) >> 1; //以中点为轴点 if ( e < A[mi] ) hi = mi; //深入前半段[lo, mi)继续查找 else if ( A[mi] < e ) lo = mi + 1; //深入后半段(mi, hi)继续查找 else return mi; //在mi处命中 } //成功查找可以提前终止 return -1; //查找失败 } //有多个命中元素时,不能保证返回秩最大者;查找失败时,简单地返回-1,而不能指示失败的位置
查找长度:
如何更为精细的评估查找算法的性能? 考察关键码的比较次数,即查找长度
通常,需要分别针对成功与失败查找,从最好、最坏、平均等角度评估
改进:失败的话,总是在最深处,左右转向不平等
思路:斐波那契查找
</pre><pre name="code" class="cpp">// 二分查找算法(版本A):在有序向量的区间[lo, hi)内查找元素e,0 <= lo <= hi <= _size template <typename T> static Rank binSearch ( T* A, T const& e, Rank lo, Rank hi ) { while ( lo < hi ) { //每步迭代可能要做两次比较判断,有三个分支 Rank mi = ( lo + hi ) >> 1; //以中点为轴点 if ( e < A[mi] ) hi = mi; //深入前半段[lo, mi)继续查找 else if ( A[mi] < e ) lo = mi + 1; //深入后半段(mi, hi)继续查找 else return mi; //在mi处命中 } //成功查找可以提前终止 return -1; //查找失败 } //有多个命中元素时,不能保证返回秩最大者;查找失败时,简单地返回-1,而不能指示失败的位置
#include "..\fibonacci\Fib.h" //引入Fib数列类 // Fibonacci查找算法(版本A):在有序向量的区间[lo, hi)内查找元素e,0 <= lo <= hi <= _size template <typename T> static Rank fibSearch ( T* A, T const& e, Rank lo, Rank hi ) { Fib fib ( hi - lo ); //用O(log_phi(n = hi - lo)时间创建Fib数列 while ( lo < hi ) { //每步迭代可能要做两次比较判断,有三个分支 while ( hi - lo < fib.get() ) fib.prev(); //通过向前顺序查找(分摊O(1))——至多迭代几次? Rank mi = lo + fib.get() - 1; //确定形如Fib(k) - 1的轴点 if ( e < A[mi] ) hi = mi; //深入前半段[lo, mi)继续查找 else if ( A[mi] < e ) lo = mi + 1; //深入后半段(mi, hi)继续查找 else return mi; //在mi处命中 } //成功查找可以提前终止 return -1; //查找失败 } //有多个命中元素时,不能保证返回秩最大者;失败时,简单地返回-1,而不能指示失败的位置
</pre><pre name="code" class="cpp">// 二分查找算法(版本A):在有序向量的区间[lo, hi)内查找元素e,0 <= lo <= hi <= _size template <typename T> static Rank binSearch ( T* A, T const& e, Rank lo, Rank hi ) { while ( lo < hi ) { //每步迭代可能要做两次比较判断,有三个分支 Rank mi = ( lo + hi ) >> 1; //以中点为轴点 if ( e < A[mi] ) hi = mi; //深入前半段[lo, mi)继续查找 else if ( A[mi] < e ) lo = mi + 1; //深入后半段(mi, hi)继续查找 else return mi; //在mi处命中 } //成功查找可以提前终止 return -1; //查找失败 } //有多个命中元素时,不能保证返回秩最大者;查找失败时,简单地返回-1,而不能指示失败的位置
</pre>忙了一个周之后又放了两个周的暑假,啥也没学,罪过罪过<p></p><p></p><p>有序向量的查找算法:</p><p></p><p>统一接口</p><p></p><pre name="code" class="cpp">template <typename T> //在有序向量的区间[lo, hi)内,确定不大于e的最后一个节点的秩 Rank Vector<T>::search ( T const& e, Rank lo, Rank hi ) const { //assert: 0 <= lo < hi <= _size return ( rand() % 2 ) ? //按各50%的概率随机使用二分查找或Fibonacci查找 binSearch ( _elem, e, lo, hi ) : fibSearch ( _elem, e, lo, hi ); }
语义约定
至少应该便于有序向量自身的维护 V.insert (1+V.search(e),e)
即便失败,也应该给出新元素适当的插入位置
若允许重复元素,则每一组也需按其插入的次序排列
约定:在有序向量区间中,确定不大于e的最后一个元素,如果e小于区间内任意数,则返回lo-1(左侧哨兵),如果大于,则返回hi-1(末元素 = 右侧哨兵左邻)
版本A:
原理
减而治之:以任意元素 x=s[mi]为界,都可将待查找区间分为三部分
这个地方就是那种最简单的二分查找的算法,不写了。
</pre><pre name="code" class="cpp">// 二分查找算法(版本A):在有序向量的区间[lo, hi)内查找元素e,0 <= lo <= hi <= _size template <typename T> static Rank binSearch ( T* A, T const& e, Rank lo, Rank hi ) { while ( lo < hi ) { //每步迭代可能要做两次比较判断,有三个分支 Rank mi = ( lo + hi ) >> 1; //以中点为轴点 if ( e < A[mi] ) hi = mi; //深入前半段[lo, mi)继续查找 else if ( A[mi] < e ) lo = mi + 1; //深入后半段(mi, hi)继续查找 else return mi; //在mi处命中 } //成功查找可以提前终止 return -1; //查找失败 } //有多个命中元素时,不能保证返回秩最大者;查找失败时,简单地返回-1,而不能指示失败的位置
查找长度:
如何更为精细的评估查找算法的性能? 考察关键码的比较次数,即查找长度
通常,需要分别针对成功与失败查找,从最好、最坏、平均等角度评估
改进:失败的话,总是在最深处,左右转向不平等
思路:斐波那契查找
</pre><pre name="code" class="cpp">// 二分查找算法(版本A):在有序向量的区间[lo, hi)内查找元素e,0 <= lo <= hi <= _size template <typename T> static Rank binSearch ( T* A, T const& e, Rank lo, Rank hi ) { while ( lo < hi ) { //每步迭代可能要做两次比较判断,有三个分支 Rank mi = ( lo + hi ) >> 1; //以中点为轴点 if ( e < A[mi] ) hi = mi; //深入前半段[lo, mi)继续查找 else if ( A[mi] < e ) lo = mi + 1; //深入后半段(mi, hi)继续查找 else return mi; //在mi处命中 } //成功查找可以提前终止 return -1; //查找失败 } //有多个命中元素时,不能保证返回秩最大者;查找失败时,简单地返回-1,而不能指示失败的位置
#include "..\fibonacci\Fib.h" //引入Fib数列类 // Fibonacci查找算法(版本A):在有序向量的区间[lo, hi)内查找元素e,0 <= lo <= hi <= _size template <typename T> static Rank fibSearch ( T* A, T const& e, Rank lo, Rank hi ) { Fib fib ( hi - lo ); //用O(log_phi(n = hi - lo)时间创建Fib数列 while ( lo < hi ) { //每步迭代可能要做两次比较判断,有三个分支 while ( hi - lo < fib.get() ) fib.prev(); //通过向前顺序查找(分摊O(1))——至多迭代几次? Rank mi = lo + fib.get() - 1; //确定形如Fib(k) - 1的轴点 if ( e < A[mi] ) hi = mi; //深入前半段[lo, mi)继续查找 else if ( A[mi] < e ) lo = mi + 1; //深入后半段(mi, hi)继续查找 else return mi; //在mi处命中 } //成功查找可以提前终止 return -1; //查找失败 } //有多个命中元素时,不能保证返回秩最大者;失败时,简单地返回-1,而不能指示失败的位置
</pre><pre name="code" class="cpp">// 二分查找算法(版本A):在有序向量的区间[lo, hi)内查找元素e,0 <= lo <= hi <= _size template <typename T> static Rank binSearch ( T* A, T const& e, Rank lo, Rank hi ) { while ( lo < hi ) { //每步迭代可能要做两次比较判断,有三个分支 Rank mi = ( lo + hi ) >> 1; //以中点为轴点 if ( e < A[mi] ) hi = mi; //深入前半段[lo, mi)继续查找 else if ( A[mi] < e ) lo = mi + 1; //深入后半段(mi, hi)继续查找 else return mi; //在mi处命中 } //成功查找可以提前终止 return -1; //查找失败 } //有多个命中元素时,不能保证返回秩最大者;查找失败时,简单地返回-1,而不能指示失败的位置
- 【数据结构与算法学习笔记】PART2 向量(接口与实现,可扩充向量,无序向量,有序向量)
- 算法与数据结构--向量
- 数据结构与算法笔记 —— 向量
- 数据结构与算法---向量使用
- Unity3D_算法与数据结构(向量)
- Java基于可扩充数组的向量实现(算法源码)
- java 可扩充数组实现向量
- 无序向量
- 【数据结构与算法学习笔记】PART3 线性结构(除向量外,数组、栈、队列、链表)
- 向量与向量空间(vector space)
- 行向量与列向量
- 向量
- 向量
- 向量
- 向量
- 向量
- 向量
- 向量
- 南阳理工ACM596
- "svn: E200033: database is locked"解决办法。(转)
- Scala Actor并发编程
- linux桌面操作系统安装eagle
- 【CDOJ 1355】柱爷与三叉戟不得不说的故事 【状压DP+子集枚举】
- 【数据结构与算法学习笔记】PART2 向量(接口与实现,可扩充向量,无序向量,有序向量)
- String深入理解
- eclipse xml 注释快捷键
- TextView文本复制功能的实现方式
- Android Studio之gradle的配置与介绍
- 用listview做联动菜单,省市区为例,附带省市区json文件
- 用Fiddler查看 Android/iOS 网络请求
- webp格式图片转换问题解决方法
- Struts2参数封装