您的位置:首页 > 理论基础 > 数据结构算法

数据结构-STL模板-学习笔记

2008-07-02 15:48 375 查看
第十五章 递归编程
学习目的:
 1.分而治之策略.
2.分而治之策略算法的的应用:O(nlog2n)归并排序和快速排序算法,
3.递归算法
4.递归算法的应用:列举集合的所有子集和n个数字的所有可能排列.
5.自顶向下的动态程序设计提高递归算法的性能.
6.自顶向下的动态程序设计的应用: 著名的背包问题.
7.八皇后问题递归回溯策略.

第一节 分而治之算法
分而治之算法是一种使用递归的问题解决算法.主要技七是将问题细分为多个子问题,而这些子问题可以作为终止条件来解决,或者可在一个递归步骤中解决.所有子问题的解答结合起来就构成了原问题的解答.

应用1:创建标尺
创建有不同大小标记的标尺只是分而治之技术的简单应用.典型的标尺就是由长度为一英寸的单元构成的序列,第个单元的末端有着最长的记号.每个一寸单元在1/2英寸处的记号要比末端的短,然后,在1/4英寸距离处的记号又要比1/2英寸的短,等等.
编写一个程序,在一条线上按规则间隔来绘制标记,在特定位置有特定大小的记号.

程序实现:
// static 是起隐藏的作用.
static void DrawMarker(double mid,int h)
{

}

void DrawRuler(double low,double high,int h)
{

if ( h>=1)
{
double mid = (low+high)/2;
// 画中间
DrawMarker(mid,h);
// 画左边
DrawRuler(low,mid,h-1);
// 画右边
DrawRuler(mid,high,h-1);
}
}

应用2:归并排序
归并的含义就是将两个或两个以上的有序数据序列合并成一个新的有序数据序列,基本思想就是假设数组A 有N个元素,那么可以看成数组A是又N个有序的了序列组,每个了序列的长度为1,然后再两两合并,得到一个N/2个长度为2或1的有序了序列,再两两合并,如些重复,直到得到一个长度为N的有序数据序列为止.

初始值 [49] [38] [65] [97] [76] [13] [27]

看成由长度为1的7个子序列组成

第一次合并之后 [38 49] [65 97] [13 76] [27]

看成由长度为1或2的4个子序列组成

第二次合并之后 [38 49 65 97] [13 27 76]

看成由长度为4或3的2个子序列组成

第三次合并之后 [13 27 38 49 65 76 97]

实现归并排序:
归并排序算法的实现可以简化为两步:
第一步先将原来的数据分为排好序的子表
第二步使用merge()函数把子表归并为有序表.

实现程序:
template <typename T>
void Merge(vector<T>& v, int first,int mid,int last)
{
vector<T> tempVector;
int indexA,indexB,indexV;

// 设置indexA扫描sublistA(索引范围是[first,mid]);
// 设置indexA扫描sublistB(索引范围是[mid,last]);
indexA = first;
indexB = mid;

//在没有比较两个子表的情况下,比较v[indexA]和v[indexB],使用push_back()
//将最小值复制到tempVector

while ( indexA<mid && indexB < last)
{
if ( v[indexA] < v[indexB] )
{
tempVector.push_back(v[indexA]);
indexA++;
}
else
{
tempVector.push_back(v[indexB]);
indexB++;
}
}

while ( indexA< mid)
{
tempVector.push_back(v[indexA++]);
}

while ( indexB < last)
{
tempVector.push_back(v[indexB++]);
}

indexA = first;

for(indexV = 0; indexV < (int)(tempVector.size()); indexV++)
{
v[indexA++] = tempVector[indexV];
}
}

template <typename T>
void MergeSort( vector<T>& v ,int first,int last)
{
// 如果子表存在多个元素,则继续
if (first+1<last)
{
int mid = (last+first)/2;
// 左边子表
MergeSort(v,first,mid);
// 右边子表
MergeSort(v,mid,last);
// 合并
Merge(v,first,mid,last);
}
}

应用3: 快速排序

快速排序算法采用另一种形式的分而治之方法.这是由C.A.R发明的方法,它使用一系列递归调用将数据表划分为一个个越来越小的子表,划分依据是中点元素的值.每一步调用都将其处理表中点的元素作为中心值.划分算法执行一系交换,以便为中心值找到合适的最终位置,左子表的所有元素都小于或等于中心值,而右子表中所有的元素都大于或等于中心值.
下面看一个实例来研究一下这种算法.
待排序的值
v={800,150,300,650,550,500,400,350,450,900}
第0级划分:表v的索引范围为[first,last) = [0,10). 这个范围的中点元素就成为了中心值.
中心值 = v[mid],其中mid = (last+first)/2;
mid = (10+0) / 2 = 5;
pivot = v[mid] = v[5];

快速排序将V的元素划分为两个子表Sl和Sh,子表Sl的元素都小于或等于中心值,称为左子表;而右子表Sh的元素都 大于或等于中心值.中心值(500)最终放在这两个子表之间的位置.首先将中心值与左子表中最前面的那个元素(索引值为0)进行交换,也就是v[first]和v[mid]交换.然后再扫描其余的元素,索引范围为[first+1,last],扫描时使用了两个指针:scanUp和scanDown.整数指针scanUp从first+1开始,逐渐往右子表移,处理对象是子表Sl中的元素.整数指针scanDown从lst-1开始往左移,处理对象是子表Sh中的元素.

扫描过程首先使用scanUp搜索大于或等于中心值的元素,这个元素将被移到子表Sh.为了确定这个元素在子表Sh中的位置,扫描过程又使用scanDown向左搜索小于或等于中心值的元素,找到这个属于子表Sl的元素后,就暂停扫描.这时指针scanUp和scanDown指着一对元素,它们都处于它们不属于的子表中:v[scnaUp]>=pivot和v[scanDown]<=pivot.为了解决这个问题,将交换这两个元素,然后将两个指针分别指向各自的下一个元素.接着重新开始使用scanUp扫描.
//交换元素,将它们放到适当的子表中
exchange(v[scanUp],v[scanDown]);
scanUp++; // 将scanUp设置为表中向右的下一个元素
scanDown++; // 将scanDown设置为表中向左的下一个元素.
在本例中,scanUp和scanDown分别在3和8终止,相应的元素是650和450,在交换650和450之后,两个下票的值分别变为4和7.

理解这个使用指针scanUp和scanDown一前一后扫描的过程很关键.下一步扫描将确定下一对需要重新调整位置的元素,它们都是不满足所处子表的条件的元素.在这个例子中,先使用scanUp开始扫描,暂停在v[4]550>=500.而后再使用scanDown进行扫描,暂停在v[7]=350<=500.v[4]和v[7]就是要找的一个元素,因此将它们进行交换.

继续扫描.最后两个指针互相靠在一起,也就是到达了将表分为两个子表的位置:scanUp指向v[5]=800,而scanDown指向v[6]=400.将两个元素交换,并更新两个下标值,这时两个下票值发生了交叉,并满足条件:scanUp>=scanDown.

一旦指针scanDown小于或才等于scanUp,划分子表过程就终止.指针scanDown指向的就是两个子表的分界点,同是也是暂时存放在v[0]的中心值的最终位置,交换v[0]和v[scanDown],将中心值放到这个位置.

上述过程就是第一轮排序的结果.以此类推就最终完成一个排序.
程序实现:
template <typename T>
int PivotIndex(vector<T>& v, int first,int last)
{
if ( first == last )
return last;
else if ( first == last - 1)
return first;
else
{
int mid = (last + first)/2;
T pivot = v[mid];

// 交换中心值和此范围的低端,并初始化scanUp和scanDown
v[mid] = v[first];
v[first]= pivot;

int scanUp = first + 1;
int scanDown = last-1;

// 管理索引,找到位于错误子表中的元素,在scanDown <=scanUp时停止.
for(;;)
{
// 在左边的子表中向右移,在scanUp进入右边的子表时,或确定某个元素>=pivot时停止
while(scanUp<=scanDown && v[scanUp] < pivot )
scanUp++;
// 在右边的子表中向右移,在scanUp进入右边的子表时,或确定某个元素<=pivot时停止;确保在v[first]处停止
while(pivot<v[scanDown])
scanDown--;

// 如果索引不在其子表中,则完成划分
if ( scanUp >= scanDown )
break;
T temp = v[scanUp];
v[scanUp] = v[scanDown];
v[scanDown]= temp;
scanUp++;
scanDown--;
}

v[first]= v[scanDown];
v[scanDown] = pivot;
return scanDown;
}
}

template <typename T>
void QuickSort(vector<T>& v, int first,int last)
{
int pivotLoc;
T temp;

if ( last - first <= 1)
return;
else if ( last - first == 2 )
{
if ( v[last-1] < v[first] )
{
temp = v[last-1];
v[last-1] = v[first];
v[first] = temp;
}
return ;

}
else
{
pivotLoc = PivotIndex(v,first,last);
QuickSort( v, first,pivotLoc);
QuickSort( v, pivotLoc+2,last);

}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: