一些常用的算法与数据结构

哈希函数

哈希法,又称散列法、杂凑法、关键字地址计算法。这种方法的中心思想是,首先在元素的关键字k和存储位置p之间建立一个对应关系f,使得p=f(k),f称为哈希函数。
创建哈希表时,把关键字为k的元素直接存入地址为f(k)的单元,以后当查找关键字为k的元素时,再利用哈希函数计算出该元素所存储的位置p=(k),从而达到按关键字直接存取元素的目的。

哈希函数的构造方法:
1、数字分析法,如果关键字中有分布较为均匀的部分,则可以使用这几位为哈希地址。例如关键字是4位整数d1d2d3d4,其中d2和d4取值均匀,那么哈希函数可以设为h(key)=h(d1d2d3d4)=d2d4。
2、平方取中发,如果不能确定关键字中那几位分布较为均匀,那么可以先求出关键字的平方值,然后按需要取平方值的中间几位作为哈希地址。这是因为平方后中间几位和关键字中每一位都相关,故不同关键字会以较高的概率产生不同的哈希地址。
3、分段叠加法,这种方法是将关键字按照哈希地址表的位数进行分割,分成几个部分,然后将每一部分相加,舍弃最高进位后得到哈希地址。具体方法有折叠法和移位法。移位法是将每一部分按照低位对齐的方式相加,折叠法是将关键字之字形排列后相加。
4、除余数法,哈希表长为m,那么取小于等于m的最大质数p,那么哈希函数为h(k)=k%p,其中%是模p取余运算。如果m与p较小,那么出现冲突的几率会比较大,这时可以取较大的m和p,比如大于m的最小质数,如果还是不行,那么重复上步。
5、伪随机数法,采用一个伪随机数函数作为哈希函数,即h(key)=random(key)。实际应用中考虑实际情况选择不同的random函数。考虑使用方法时,要考虑的因素有5个,计算哈希函数所需要的时间、关键字的长度、哈希表大小、关键字分布情况、jiluchazhaopinl

哈希函数处理冲突:创建哈希表和查找哈希表都会遇到冲突,两种情况下解决冲突的方法应该是一致的。
(1) 开放地址法,这种方法又称再散列法,其基本思想是:当关键字key的哈希地址p=H(key)出现冲突时,以p为基础,产生另一个哈希地址p1,如果仍然冲突,再以p为基础,产生另一个哈希地址p2,……,直到找到一个不冲突的哈希地址pi,将相应元素存入其中。
通用函数形式:Hi=(H(key)+di)%m(i=1,2,……,n)
H(key)是哈希函数,m为表长,di称为增量序列。增量序列的取值方式不同,相应的再散列方式也不同。

  • 线性探测再散列 di=1,2,3,……,m-1
  • 二次探测再散列 di=1^2,-1^2,2^2,-2^2,…,k^2,-k^2  (k<m/2) 方法特点:冲突发生时,在表的左右进行跳跃式探测,比较灵活。
  • 伪随机探测再散列 di=伪随机数序列

(2)再哈希法

 

排序

直接插入排序:
最好情况(顺序):此时总比较次数为n-1次,移动记录次数也达到最小2(n-1)(每次只对待插记录r[i]移动两次)
最坏情况(逆序):总比较次数为n(n-1)/2,记录移动的次数也是n(n+1)/2

平均时间复杂度T(n)=O(n^2),排序算法为稳定排序
直接插入排序方法简单,适合用于待排记录数目较少而且基本有序的情况。
有变种插入排序 折半插入排序,在确定插入位置时使用折半查找。
直接插入排序中,等于就视为小于。
基于插入的排序方法还有希尔排序。
r[0]置为哨兵,排序记录从r[1]开始。
直接插入排序, 排序

交换类排序法:
冒泡排序:最坏情况(逆序) 每一趟排序进行n-i次比较,3(n-i)(交换值需要三次赋值,中间变量)次移动,故时间复杂度O(n^2)。
快速排序:从序列两端交替向中间移动,将比枢轴小的都移动到low,将比枢轴大的都移动到high。一趟排序后将序列一分为二,递归调用直到low=high。每次排序都要返回枢轴位置。

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
/************************************************************************
快速排序一趟排序,r[]为待排序序列,从1开始。
left为左起标号,也是枢轴,right为右端标号。
返回枢轴位置。
************************************************************************/
int QKPass(RecordType r[],int left,int right)
{
RecordType pivotkey=r[left];
int low=left;
int high=right;
//将原序列以枢轴分割为两部分,循环结束标志为low==high
while (low&lt;high)
{
//这两个while的顺序不可颠倒
//low==high也是循环结束标志,此时进行r[low]=r[high]或者r[high]=r[low]是安全的。
while (low&lt;high&amp;&amp;r[high].key&gt;pivotkey.key)
{
high--;
}
r[low]=r[high];
while (low&lt;high&amp;&amp;r[low].key&lt;pivotkey.key)
{
low++;
}
r[high]=r[low];
}
r[low]=pivotkey;
return low;
}
/************************************************************************/
/* 递归快速排序,r[]待排序序列,low起始标号,high结束标号                      */
/************************************************************************/
void QKSort(RecordType r[],int low,int high)
{
if (low&lt;high)
{
int pos=QKPass(r,low,high);
QKSort(r,low,pos-1);
QKSort(r,pos+1,high);
}
}

快速排序的时间复杂度是O(nlogn)但是在待排序列有序或基本有序时,快速排序会退化为起泡排序,时间复杂度变为O(n^2)
冒泡排序, 快速排序, 排序

选择类排序:
简单选择排序:从左向右移动,每次都将待排序部分中最小的元素与当前元素交换。
堆排序:自堆顶至叶子的调整过程为筛选;从一个无序序列建堆的过程就是一个反复筛选的过程。若将此序列看成是一个完全二叉树,则最后一个非终端节点是[n/2]个元素,由此筛选只需要从第[n/2]个元素开始。
堆排序算法过程:1、将待排序序列建为大顶堆(小顶堆)2、将对顶元素与当前堆末尾元素互换。3、将末尾元素排除堆,堆规模减一。4、重复1至3步,直到堆规模为1。

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
/************************************************************************
一次筛选从指定节点开始往下筛选出最大值,
r[]待排序序列(从1开始),s指定节点,length序列长
************************************************************************/
void HeapAdjust(RecordType r[],int s,int length)
{
RecordType temp=r[s];
for (int j=2*s;j<=length;j*=2)
{
//若要建小顶堆则第二个条件改为r[j].key>r[j+1].key
if (j<length&&r[j].key<r[j+1].key)
{
j++;
}
//若要建小顶堆则条件改为temp.key<r[j].key
if (temp.key>r[j].key)
{
break;//此时temp比它的左右孩子都大,故位置应该不变,所以跳出;
}
r[s]=r[j];
s=j;
}
r[s]=temp;
}
/************************************************************************
堆排序,用大顶堆将序列按升序排列(降序则用小顶堆)
1、将待排序序列建为大顶堆
2、将对顶元素与当前堆末尾元素互换。
3、将末尾元素排除堆,堆规模减一。
4、重复1至3步,直到堆规模为1。
r[] 待排序列,length序列长
************************************************************************/
void HeapSort(RecordType r[],int length)
{
//建大(小)顶堆
for (int i=length/2;i>0;i--)
{
HeapAdjust(r,i,length);
}
//进行n-1次筛选,每次筛选出一个待排序列中的最大(小)值放于堆尾
//每一次筛选堆的规模减一
for (int i=length;i>1;--i)
{
RecordType temp=r[i];
r[i]=r[1];
r[1]=temp;
HeapAdjust(r,1,i-1);
}
}

堆排序在最坏情况下,其时间复杂度也是O(nlogn),相对于快速排序来说,这是堆排序最大的优点。此外,堆排序仅需要一个记录大小供交换使用的辅助存储空间。
堆排序方法对记录数较少的文件并不值得提倡,因为其运行时间主要消耗在创建初始堆和调整新堆时进行的反复筛选。
堆排序不是稳定的排序。
排序, 堆排序

归并排序:归并排序是将两个或两个以上的有序表组合成一个新的有序表,他需要一个辅助存储空间,空间大小与待排序列一样大。

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
/************************************************************************
归并算法中的一次相邻归并,将r[lpos...rpos-1]和r[rpos...rightend]归并为一个有序序列,t[]为临时辅助空间。
************************************************************************/
void Merge(RecordType r[],RecordType t[],int lpos, int rpos, int rightend)
{
int i;
int leftend;
int numelements;
int tmppos;
 
leftend = rpos - 1;
tmppos = lpos;
numelements = rightend - lpos + 1;
 
// main loop
while ((lpos <= leftend) && (rpos <= rightend))
{
if (r[lpos].key <= r[rpos].key)
t[tmppos++] = r[lpos++];
else
t[tmppos++] = r[rpos++];
}
 
while (lpos <= leftend)     // copy rest of first half
t[tmppos++] = r[lpos++];
while (rpos <= rightend)    // copy rest of second half
t[tmppos++] = r[rpos++];
 
// copy tmp back
for (i = 0; i < numelements; ++i, --rightend)
r[rightend] = t[rightend];
}
/************************************************************************
递归调用 实现归并排序
************************************************************************/
inline void MSort(RecordType x[], RecordType tmp[], int left, int right)
{
int center;
 
if (left < right)
{
center = (left + right) / 2;
MSort(x, tmp, left, center);
MSort(x, tmp, center + 1, right);
Merge(x, tmp, left, center + 1, right);
}
}
/************************************************************************
归并排序主函数,此函数完成临时辅助空间的申请和释放
************************************************************************/
inline void MergeSort(RecordType x[], int n)
{
RecordType *tmp;
 
tmp = new (RecordType[(n+1) * sizeof(RecordType)]);
if (NULL != tmp)
{
MSort(x, tmp, 1, n);
delete tmp;
}
}

这是归并排序的递归形式的2-路归并排序,递归形式在形式上较简洁,但是实用性很差。与快速和堆排序相比,归并排序是稳定的排序方法。
一般很少实用2-路归并排序法进行内部排序。
归并排序的时间复杂度也是O(nlogn)但是归并排序需要与待排序列等大小的辅助空间。

排序总结:
(1)若n较小(如n≤50),可采用直接插入或直接选择排序。
当记录规模较小时,直接插入排序较好;否则因为直接选择移动的记录数少于直接插人,应选直接选择排序为宜。
(2)若文件初始状态基本有序(指正序),则应选用直接插入、冒泡或随机的快速排序为宜;
(3)若n较大,则应采用时间复杂度为O(nlgn)的排序方法:快速排序、堆排序或归并排序。
快速排序是目前基于比较的内部排序中被认为是最好的方法,当待排序的关键字是随机分布时,快速排序的平均时间最短;
堆排序所需的辅助空间少于快速排序,并且不会出现快速排序可能出现的最坏情况。这两种排序都是不稳定的

快速排序的空间占用是实现递归时使用的。

原文地址:https://www.cnblogs.com/qingsong/p/3507138.html