目录
1、排序的基本概念
排序的一般定义
排序是计算机内经常进行的—种操作,其目的是将一组“无序”的数据元素调整为“有序”的数据元素。
排序的数学定义
假设含n个数据元素的序列为: { R1,R2 , ... ,Rn} , 其相应的关键字序列为: { K1,K2 , ... ,Kn } ;
这些关键字相互之间可以进行比较,即:在它们之间存在着这样—个关系: Kp1 <= Kp2 <=...<= Kpn
按此固有关系将上式记录序列重新排列为: { Rp1,Rp2, ... ,Rpn } 的操作称为排序。
排序的示例
按总评排序后为什么张无忌的排名比郭靖靠前呢?
排序的稳定性
如果在序列中有两个数据元素 r[i] 和 r[j],它们的关键字 k[i]== k[j],且在排序之前,对象 r[i] 排在 r[j] 前面;
如果在排序之后,对象 r[i]仍在对象 r[j] 的前面,则称这个排序方法是稳定的,否则称这个排序方法是不稳定的。
稳定性排序示例
多关键字排序
- 排序时需要比较的关键字多余一个
排序结果首先按关键字1进行排序
当关键字1相同时按关键字2进行排序
....
当关键字n-1相同时按关键字n进行排序
多关键字排序示例
对于多关键字排序,只需要在比较操作时同时考虑多个关键字即可!!
编程实验
多关键字比较操作
#include <iostream>
#include "Object.h"
using namespace std;
using namespace DTLib;
struct Test : public Object
{
int key1; //high priority
int key2;
Test(int k1, int k2)
{
key1 = k1;
key2 = k2;
}
bool operator == (const Test& t)
{
return (key1 == t.key1) && (key2 == t.key2);
}
bool operator != (const Test& t)
{
return !(*this == t);
}
bool operator < (const Test& t)
{
return (key1 < t.key1) || ((key1 == t.key1) && (key2 < t.key2));
}
bool operator >= (const Test& t)
{
return !(*this < t);
}
bool operator > (const Test& t)
{
return (key1 > t.key1) || ((key1 == t.key1) && (key2 > t.key2));
}
bool operator <= (const Test& t)
{
return !(*this > t);
}
};
int main()
{
Test t1(3, 4);
Test t2(2, 5);
Test t3(3, 4);
Test t4(3, 5);
cout << (t1 > t2) << endl; //1
cout << (t3 < t4) << endl; //1
return 0;
}
排序中的关键操作
-比较:任意两个数据元素通过比较操作确定先后次序
-交换:数据元素之间需要交换才能得到预期结果
排序的审判
-时间性能:关键性能差异体现在比较和交换的数量
-辅助存储空间:为完成排序操作需要的额外的存储空间,必要时可以“空间换时间”
-算法的实现复杂性:过于复杂的排序法可能影响可读性和可维护性
2、DTLib中的排序类设计
DTLib中的排序类 Sort.h
#ifndef SORT_H
#define SORT_H
#include "Object.h"
namespace DTLib
{
class Sort : public Object
{
private:
Sort();
Sort(const Sort&);
Sort& operator = (const Sort&);
template <typename T>
static void swap(T& a, T& b)
{
T t(a);
a = b;
b = t;
}
public:
// 排序算法
};
}
#endif // SORT_H
3、选择排序
选择排序的基本思想
每次(例如第 i 次,i = 0,1, …,n-2)从后面n-i个待排的数据元素中选出关键字最小的元素,
作为有序元素序列第 i 个元素。
第 i 次选择排序示例
选择排序的实现 Sort::Select
#ifndef SORT_H
#define SORT_H
#include "Object.h"
namespace DTLib
{
class Sort : public Object
{
private:
Sort();
Sort(const Sort&);
Sort& operator = (const Sort&);
template <typename T>
static void Swap(T& a, T& b)
{
T t(a);
a = b;
b = t;
}
public:
template < typename T >
static void Select(T array[], int len, bool min2max = true)
{
for(int i = 0; i < len; i++)
{
int min = i;
for(int j = i + 1; j < len; j++)
{
if(min2max ? array[min] > array[j] : array[min] < array[j] )
{
min = j;//记录最小元素下标
}
}
if(min != i) //追求高效代码,交换相比比较耗时
{
Swap(array[i], array[min]);
}
}
}
};
}
#endif // SORT_H
main.cpp
#include <iostream>
#include "Sort.h"
using namespace std;
using namespace DTLib;
int main()
{
int array[] = {7, 9, 4, 6, 2};
Sort::Select(array, 5);
for(int i = 0; i < 5; i++)
{
cout << array[i] << " ";
}
cout << endl;
Sort::Select(array, 5, 0);
for(int i = 0; i < 5; i++)
{
cout << array[i] << " ";
}
cout << endl;
return 0;
}
4、插入排序
插入排序的基本思想
当插入第i (i >= 1)个数据元素时,前面的V[0],V[1] ,…,V[i-1]已经排好序;
这时,用V[i]的关键字与V[i-1],V[i-2],…, V[0]的关键字进行比较,找到
位置后将V[i]插入, 原来位置上的对象向后顺移。
第 i 次插入排序示例
插入排序的实现 Sort::Insert
#ifndef SORT_H
#define SORT_H
#include"Object.h"
namespace DTLib
{
class Sort : public Object
{
private:
Sort();
Sort(const Sort&);
Sort& operator = (const Sort&);
template <typename T>
static void Swap(T& a,T& b)
{
T t(a);
a = b;
b = t;
}
public:
template < typename T >
static void Select(T array[],int len,bool min2max=true)
{
for(int i = 0; i < len; i++)
{
int min = i;
for(int j = i + 1; j < len; j++)
{
if(min2max ? array[min] > array[j] : array[min] < array[j] )
{
min = j;
}
}
if(min != i) //追求高效代码,交换相比比较耗时
{
Swap(array[i], array[min]);
}
}
}
template < typename T >
static void Insert(T array[], int len, bool min2max = true)
{
for(int i = 1; i < len; i++)
{
int k = i;
T e = array[i];
// for(int j = i - 1; j >=0 ;j--)
// {
// if(array[j] > e)
// {
// array[j+1] = array[j];
// k = j;
// }
// else
// {
// break;
// }
// }
for(int j = i - 1; j >= 0 && (min2max ? (array[j] > e) : (array[j] < e));j--)
{
array[j+1] = array[j];
k = j;
}
if(k != i)
{
array[k] = e;
}
}
}
};
}
#endif // SORT_H
main.cpp
#include <iostream>
#include "Sort.h"
using namespace std;
using namespace DTLib;
int main()
{
int array[] = {7, 9, 4, 6, 2};
Sort::Select(array, 5);
for(int i = 0; i < 5; i++)
{
cout << array[i] << " ";
}
cout << endl;
Sort::Select(array, 5, 0);
for(int i = 0; i < 5; i++)
{
cout << array[i] << " ";
}
cout << endl;
Sort::Insert(array, 5);
for(int i = 0; i < 5; i++)
{
cout << array[i] << " ";
}
cout << endl;
Sort::Insert(array, 5, 0);
for(int i = 0; i < 5; i++)
{
cout << array[i] << " ";
}
return 0;
}
5、冒泡排序
冒泡排序的基本思想
-每次从后向前进行(假设为第i次),j = n-1, n-2, ... , i,两两比较V[j-1]和V[j]的关键字;
如果发生逆序,则交换V[j-1]和V[j]。
第i次冒泡排序示例
冒泡排序的实现 Sort::Bubble
template < typename T>
static void Bubble(T array[], int len, bool min2max = true)
{
bool exchange = true;
for(int i = 0; i < len && exchange; i++) //如果exchange = false后面已经排序好,不需要排序
{
exchange = false;
for(int j = len - 1; j > i; j--)
{
if(min2max ? array[j] < array[j-1] : array[j] > array[j-1])
{
Swap(array[j], array[j-1]);
exchange = true;
}
}
}
}
main.cpp
#include <iostream>
#include "Sort.h"
using namespace std;
using namespace DTLib;
int main()
{
int array[] = {7, 9, 4, 6, 2};
Sort::Bubble(array, 5);
for(int i = 0; i < 5; i++)
{
cout << array[i] << " ";
}
cout << endl;
Sort::Bubble(array, 5, 0);
for(int i = 0; i < 5; i++)
{
cout << array[i] << " ";
}
cout << endl;
return 0;
}
6、希尔排序
希尔排序的基本思想
-将待排序列划分为若干组,在每—组内进行插入排序,以使
整个序列基本有序,然后再对整个序列进行插入排序。
希尔排序示例
例如:将n个数据元素分成d个子序列:
{ R[1] , R[1+d] , R[1+2d] , ... , R[1+kd]}
{ R[2] , R[2+d] , R[2+2d] , ... , R[2+kd]}
{ R[d] , R[2d] , R[3d] , … , R[kd] , R[(k+1)d] }
其中,d称为增量,它的值在排序过程中从大到小逐渐缩小,直至最后—趟排序减为1。
希尔排序的实现 Sort::Shell
template < typename T>
static void Shell(T array[],int len,bool min2max=true)
{
int d = len;
do
{
d = d / 3 + 1; //实践证明这样效果更好,也可以d--
for(int i=d;i<len;i+=d)
{
int k = i;
T e = array[i];
for(int j=i-d;j>=0 && (min2max ? (array[j] > e) : (array[j] < e));j-=d)
{
array[j+d] = array[j];
k = j;
}
if(k != i)
{
array[k] = e;
}
}
}while(d > 1);
}
main.cpp
#include <iostream>
#include"Sort.h"
using namespace std;
using namespace DTLib;
int main()
{
int array[] = {7,9,4,6,2};
Sort::Shell(array,5);
for(int i=0;i<5;i++)
{
cout<<array[i]<<" ";
}
cout<<endl;
Sort::Shell(array,5,0);
for(int i=0;i<5;i++)
{
cout<<array[i]<<" ";
}
cout<<endl;
return 0;
}
7、归并排序
归并排序的基本思想
-将两个或两个以上的有序序列合并成—个新的有序序列
有序序列V[0]…V[m]和V[m+1]…V[n-1] ⇢ V[0]…V[n-1]
这种归并方法称为 2 路归并
归并的套路
-将3个有序序列归并为—个新的有序序列,称为 3 路归并
-将N个有序序列归并为—个新的有序序列,称为 N 路归并
-将多个有序序列归并为—个新的有序序列,称为多路归并
2路归并示例
归并排序的代码实现
归并排序的实现 Sort::Merge
#ifndef SORT_H
#define SORT_H
#include"Object.h"
namespace DTLib
{
class Sort : public Object
{
private:
Sort();
Sort(const Sort&);
Sort& operator = (const Sort&);
template <typename T>
static void Swap(T& a,T& b)
{
T t(a);
a = b;
b = t;
}
template < typename T>
static void Merge(T src[],T helper[],int begin,int mid,int end,bool min2max) //归并
{
int i = begin;
int j = mid + 1;
int k = begin; //辅助空间起始位置
while(i <= mid && j <= end)
{
if(min2max ? (src[i] < src[j]) : (src[i] > src[j]))
{
helper[k++] = src[i++];
}
else
{
helper[k++] = src[j++];
}
}
while(i <= mid)
{
helper[k++] = src[i++];
}
while(j <= end)
{
helper[k++] = src[j++];
}
for(i=begin;i<=end;i++)
{
src[i] = helper[i];
}
}
template < typename T>
static void Merge(T src[],T helper[],int begin,int end,bool min2max)
{
if(begin < end)
{
int mid = (begin + end) / 2;
Merge(src, helper, begin, mid, min2max); //左边 二路归并排序==》左边有序
Merge(src, helper, mid+1, end, min2max); //右边 二路归并排序==》右边有序
Merge(src, helper, begin, mid, end, min2max);
}
}
public:
template < typename T>
static void Merge(T array[],int len,bool min2max=true)
{
T* helper = new T[len];
if(helper != NULL)
{
Merge(array, helper, 0, len-1, min2max);
}
delete[] helper;
}
};
}
#endif // SORT_H
main.cpp
#include <iostream>
#include"Sort.h"
using namespace std;
using namespace DTLib;
int main()
{
int array[] = {7,9,4,6,2,1,3,8,0,5};
Sort::Merge(array,10);
for(int i=0;i<10;i++)
{
cout<<array[i]<<" ";
}
cout<<endl;
Sort::Merge(array,10,0);
for(int i=0;i<10;i++)
{
cout<<array[i]<<" ";
}
cout<<endl;
return 0;
}
8、快速排序
快速排序的基本思想
-任取序列中的某个数据元素作为基准将整个序列划分为左右两个
子序列
左侧子序列中所有元素都小于或等于基准元素
右侧子序列中所有元素都大于基准元素
基准元素排在这两个子序列中间
-分别对这两个子序列重复进行划分,直到所有的数据元素都排在
相应位置上为止
快速排序示例
快速排序的实现 Sort::Quick
#ifndef SORT_H
#define SORT_H
#include"Object.h"
namespace DTLib
{
class Sort : public Object
{
private:
Sort();
Sort(const Sort&);
Sort& operator = (const Sort&);
template <typename T>
static void Swap(T& a,T& b)
{
T t(a);
a = b;
b = t;
}
template < typename T >
static int Partition(T array[],int begin,int end,bool min2max)
{
T pv = array[begin];
while(begin < end)
{
while( (begin < end) && ( min2max ? (array[end] > pv) : (array[end] < pv) ) )
{
end--;
}
Swap(array[begin],array[end]);
while( (begin < end) && ( min2max ? (array[begin] <= pv) : (array[begin] >= pv) ) )
{
begin++;
}
Swap(array[begin],array[end]);
}
array[begin] = pv;
return begin;
}
template < typename T >
static void Quick(T array[],int begin,int end,bool min2max)
{
if(begin < end)
{
int pivot = Partition(array,begin,end,min2max);
Quick(array,begin,pivot-1,min2max);
Quick(array,pivot+1,end,min2max);
}
}
public:
template < typename T >
static void Quick(T array[],int len,bool min2max=true)
{
Quick(array,0,len-1,min2max);
}
};
}
#endif // SORT_H
main.cpp
#include <iostream>
#include"Sort.h"
using namespace std;
using namespace DTLib;
int main()
{
int array[] = {7,9,4,6,2,1,3,8,0,5};
Sort::Quick(array,10);
for(int i=0;i<10;i++)
{
cout<<array[i]<<" ";
}
cout<<endl;
Sort::Quick(array,10,0);
for(int i=0;i<10;i++)
{
cout<<array[i]<<" ";
}
cout<<endl;
return 0;
}
9、排序的工程应用示例
排序类(Sort)与数组类(Array)的关系
编程实验
排序类与数组类的关系 Array.h Sort.h
Array.h提供
T* array() const
{
return m_array;
}
Sort.h新增
template < typename T >
static void Insert(Array<T>& array,bool min2max=true)
{
Insert(array.array(), array.length(), min2max);
}
template < typename T >
static void Select(Array<T>& array,bool min2max=true)
{
Select(array.array(), array.length(), min2max);
}
template < typename T >
static void Bubble(Array<T>& array,bool min2max=true)
{
Bubble(array.array(), array.length(), min2max);
}
template < typename T >
static void Shell(Array<T>& array,bool min2max=true)
{
Shell(array.array(), array.length(), min2max);
}
template < typename T >
static void Merge(Array<T>& array,bool min2max=true)
{
Merge(array.array(), array.length(), min2max);
}
template < typename T >
static void Quick(Array<T>& array,bool min2max=true)
{
Quick(array.array(), array.length(), min2max);
}
main.cpp
#include <iostream>
#include "StaticArray.h"
#include "Sort.h"
using namespace std;
using namespace DTLib;
int main()
{
StaticArray<double, 5> sa;
for(int i=0; i<5; i++)
{
sa[i] = i;
}
Sort::Insert(sa);
for(int i=0; i<sa.length(); i++)
{
cout << sa[i] << " ";
}
cout << endl;
Sort::Shell(sa, false);
for(int i=0; i<sa.length(); i++)
{
cout << sa[i] << " ";
}
return 0;
}
当待排数据元素为体积庞大的对象时,如何提高排序的效率?
问题分析
-排序过程中不可避免的需要进行交换操作
-交换操作的本质为数据元素间的相互复制
-当数据元素体积较大时,交换操作耗时巨大
解决方案:代理模式
1. 为待排数据元素设置代理对象
2. 对代理对象所组成的序列进行排序
3. 需要访问有序数据元素时,通过访问代理序列完成
编程实验
解决方案 Proxy Pattern
#include <iostream>
#include <ctime>
#include "Sort.h"
using namespace std;
using namespace DTLib;
struct Test : public Object
{
int id;
int data1[1000];
double data2[1000];
bool operator < (const Test& obj)
{
return (id < obj.id); // 排序时按照id进行排序
}
bool operator <= (const Test& obj)
{
return (id <= obj.id);
}
bool operator > (const Test& obj)
{
return (id > obj.id);
}
bool operator >= (const Test& obj)
{
return (id >= obj.id);
}
};
class TestProxy : public Object
{
protected:
Test* m_pTest;
public:
// 原始对象实现的功能代理对象也实现
int id()
{
return m_pTest->id;
}
int* data1()
{
return m_pTest->data1;
}
double* data2()
{
return m_pTest->data2;
}
Test& test() const
{
return *m_pTest;
}
bool operator < (const TestProxy& obj)
{
return test() < obj.test(); // 被代理的原始对象的比较
}
bool operator <= (const TestProxy& obj)
{
return test() <= obj.test();
}
bool operator > (const TestProxy& obj)
{
return test() > obj.test();
}
bool operator >= (const TestProxy& obj)
{
return test() >= obj.test();
}
Test& operator = (Test& test)
{
m_pTest = &test;
return test;
}
};
Test t[1000];
TestProxy pt[1000]; // 创建代理数组,对代理对象进行排序
int main()
{
clock_t begin = 0;
clock_t end = 0;
for(int i=0; i<1000; i++)
{
t[i].id = i;
pt[i] = t[i]; // 代理
}
begin = clock();
Sort::Bubble(t, 1000, false);
end = clock();
cout << "Time: " << (end - begin) << endl;
return 0;
}
修改:
Sort::Bubble(pt, 1000, false);
10、小结
排序是数据元素从无序到有序的过程
排序具有稳定性,是选择排序算法的因素之—
比较和交换是排序的基本操作
多关键字排序与单关键字排序无本质区别
排序的时间性能是区分排序算法好坏的主要因素
选择排序每次选择未排元素中的最小元素
插入排序每次将第 i 个元素插入前面 i-1 个已排元素中
选择排序是不稳定的排序法,插入排序是稳定的排序方法
选择排序和插入排序的时间复杂度为O(n2)
冒泡排序每次从后向前将较小的元素交互到位
冒泡排序是—种稳定的排序法,其复杂度为O(n2)
希尔排序通过分组的方式进行多次插入排序
希尔排序是—种不稳定的排序法,其复杂度为O(n3/2)
归并排序需要额外的辅助空间才能完成,空间复杂度为O(n)
归并排序的时间复杂度为O(n*logn) , 是—种稳定的排序法
快速排序通过递归的方式对排序问题进行划分
快速排序的时间复杂度为O(n*logn) , 是—种不稳定的排序法
DTLib中的排序类和数组类之间存在关联关系
排序类能够对数组类对象进行排序
当排序体积庞大的对象时,使用代理模式间接完成
代理模式的使用有效避开大对象交换时的耗时操作
代理模式解决方案是空间换时间思想的体现