【数据结构与算法】程序内功篇七--查找与排序


一、查找

1、查找概念

记录表L=(R1 R2……Rn),其中Ri(l≤i≤n)记录,对给定的某个值k,在表L中确定key=k记录的过程,称为查找

表L中存在一个记录Rikey=k,记为Ri.key=k,则查找成功,返回该记录在表L中的序号i(或Ri 的地址),否则(查找失败)返回0(或空地址Null)。


2、平均查找长度

查找算法,主要分析其T(n)。查找过程是key比较过程,时间主要耗费在各记录的key与给定k值的比较上。比较次数越多,算法效率越差(即T(n)量级越高),故用“比较次数”刻画算法的T(n)。

一般以“平均查找长度”来衡量T(n)

平均查找长度ASL(Average Search Length):
对给定k,查找表L中记录比较次数的期望值(或平均值),即:
在这里插入图片描述

Pi为查找Ri的概率。等概率情况下Pi=1/nCi为查找Rikey比较次数(或查找次数)。


3、查找方法

(1)顺序查找

顺序表,是将表中记录(R1 R2……Rn)按其序号存储于一维数组空间,查找时遍历整个数组
①记录Ri的类型描述如下:

typedef struct
 { keytype key;    //记录key//
	     ……   //记录其他项//
 } Retype;

②顺序表类型描述如下:

#define maxn 1024     //表最大长度//
typedef struct 
{   Retype data[maxn];  //顺序表空间//
 	 int len;   //当前表长,表空时len=0//
} sqlist;

说明:sqlist r,则(r.data[1],……,r.data[r.len])为记录表(R1……Rn), Ri.key为r.data[i].key, r.data[0]称为监视哨,为算法设计方便所设。

算法思路:
设给定值为k,在表(R1 R2……Rn)中,从Rn开始,查找key=k的记录。

int sqsearch(sqlist r, keytype k)  
	{   int i;
                 r.data[0].key = k;  //k存入监视哨//
	      i = r.len;  //取表长//
	     while(r.data[i].key != k) i--;  
	     return (i);
	}

③设Ci(1≤i≤n)查找i记录的key比较次数(或查找次数):

	若r.data[n].key = k,       Cn=1;
	若r.data[n-1].key = k,    Cn-1=2;
   		    ……
	若r.data[i].key = k,        Ci=n-i+1;
   		    ……
	若r.data[1].key = k,       C1=n

ASL = O(n)。而查找失败时,查找次数等于n+l,同样为O(n)

查找算法,若ASL=O(n),则效率是很低的,意味着查找某记录几乎要扫描整个表,当表长n很大时,会令人无法忍受。


(2)折半查找

对给定值k逐步确定待查记录所在区间,每次将搜索空间减少一半(折半),直到查找成功失败为止
设两个游标low、high,分别指向当前待查找表的上界(表头)和下界(表尾)mid指向中间元素

例如:查找k=20的记录
在这里插入图片描述

因此代码为:

int Binsearch(sqlist r, keytype k)    //对有序表r折半查找的算法//
{  int low, high, mid;  low = 1;high = r.len; 
    while (low <= high)    
    {  mid = (low+high) / 2;   
        if (k == r.data[mid].key)  return (mid);  
        if (k < r.data[mid].key) high = mid-1;  
        else low = mid+1;
    }      
     return(0);
 }  

注:一定要在有序的情况下才能进行折半查找。


(3)分块查找

记录表长n,将表的n个记录分成b=n/s个块,每块s个记录(最后一块记录数可以少于s个),即:
在这里插入图片描述
且表分块有序,即第i(1≤i≤b-1)块所有记录的key小于第i+1块中记录的key,但块内记录可以无序

构建方法:

  • 建立索引
  • ②每块对应一索引项
  • ③其中kmax为该块内记录的最大keylink为该块第一记录的序号(或指针)。
    在这里插入图片描述

例如:设表长n=19,取s=5,b=19/5= 4,
分块索引查找分两步进行:
(1)由索引表确定待查找记录所在的块;
(2)在块内顺序查找。
在这里插入图片描述
如查找k=19的记录
索引表是按照kmax有序的,可对其折半查找。而块内按顺序方法查找。


(4)Hash表查找(重点)

(1)哈希查找的来由

理想的查找方法是:对给定的k不经任何比较便能获取所需的记录,其查找的时间复杂度为常数级O( C )
这就要求在建立记录表的时候,确定记录的key与其存储地址之间的关系f,即使key与记录的存放地址H相对应:
在这里插入图片描述
当要查找key=k的记录时,通过关系f就可得到相应记录的地址而获取记录,从而免去了key的比较过程
这个关系f就是所谓的Hash函数(或称散列函数、杂凑函数),记H(key)
它实际上是一个地址映象函数,其自变量为记录的key函数值为记录的存储地址(或称Hash地址)。

注:

  • 不同的key可能得到同一个Hash地址,即当keyl≠key2时,可能有H(key1)=H(key2),此时称key1和key2为同义词。这种现象称为“冲突”或“碰撞”,因为一个数据单位只可存放一条记录
  • 一般,选取Hash函数只能做到使冲突尽可能少,却不能完全避免。这就要求在出现冲突之后,寻求适当的方法来解决冲突记录的存放问题。

选取(或构造)Hash函数的方法很多,原则是尽可能将记录均匀分布,以减少冲突现象的发生。以下介绍几种常用的构造方法。

  • 直接地址法
  • 平方取中法
  • 叠加法
  • 保留除数法
  • 随机函数法

(2)保留除数法

又称质数除余法,设Hash表空间长度为m,选取一个不大于m的最大质数p,令:H(key)=key%p

设记录的key集合k={28,35,63,77,105……},
若选取p=21=3*7(包括质数因子7),有:
        key:28  35  63  77  105  ……
       H(key)=key%21: 7   14   0   14    0    ……
使得包含质数因子7的key都可能被映象到相同的单元,冲突现象严重。

若取p=l9(质数),同样对上面给定的key集合k,有:
       key:28  35  63  77  105
       H(key)=key%19: 9   16   6    1    10
H(key)的随机度就好多了。

(3)处理冲突的方法

处理冲突的方法一般为:在地址j前面或后面找一个空闲单元存放冲突的记录,或将相互冲突的值记录拉成链表

在处理冲突的过程中,可能发生一连串的冲突现象,
即可能得到一个地址序列H1、H2……Hn,Hi∈[0,m-l]。
H1是冲突时选取的下一地址,而H1中可能己有记录,
又设法得到下一地址H2……直到某个Hn不发生冲突为止。
这种现象称为“聚积”,它严重影响了Hash表的查找效率。

一个因素是表的装填因子αα=n/m,其中m为表长n为表中记录个数。一般α0.7~0.8之间,使表保持一定的空闲余量,以减少冲突和聚积现象。

(4)开放地址法
H(key)=key%13
在这里插入图片描述
(5)链地址法

发生冲突时,将各冲突记录链在一起,即同义词的记录存于同一链表

设H(key)取值范围(值域)为[0,m-l],
建立头指针向量HP[m],
HP[i](0≤i≤m-l)初值为空。
设H(key)=key%13        
k={ 23,34,14,38,46,
16,68,15,07,31,26 }

在这里插入图片描述

链地址法解决冲突的优点:无聚积现象;删除表中记录容易实现。


(5)Hash表的程序实行

这里我们选择使用保留除数法构造函数,使用链地址法解决冲突问题

(1)结构体定义

#define DEBUG 1
#define N 14

typedef int data_t;
typedef struct node   //哈希表结点定义
{
	data_t value;  //存入的数据
	data_t key;    //表中的位置,key = value % N
	struct node *next; //下一结点的指针
}listhash,*linkhash;
typedef struct   //哈希表定义
{
	listhash a[N]; 
}hash;

(2)哈希表的创建

/**
 * @description:哈希表的创建 
 * @param {*}
 * @return {哈希表的地址}
 */
hash *create_hash()
{
	hash *HT = (hash *)malloc(sizeof(hash));  //为表开辟空间  
	if(HT == NULL)
	{
		#if DEBUG
		printf("hash create error!\n");
		#endif
		return 0;
	}

	memset(HT, 0, sizeof(hash));   //表中清0

	return HT;
}

(3)数据存入进哈希表

/**
 * @description: 数据存入进哈希表 
 * @param {hash} *HT-哈希表指针
 * @param {data_t} value-存入的数据
 * @return {0-函数失败,1-函数成功}
 */
int Add_hash(hash *HT, data_t value)
{
	linkhash p,q;
	if(HT == NULL)
	{
		#if DEBUG
		printf("HT is NULL!\n");
		#endif
		return 0;
	}

	if((p = (linkhash)malloc(sizeof(listhash))) == NULL)  //结点开辟空间
	{
		#if DEBUG
		printf("node create error!\n");
		#endif
		return 0;
	}

	p->value = value;   //结点存入数据
	p->key = value % N;
	p->next = NULL;

	q = &(HT->a[p->key]);   
	while(q->next != NULL && q->next->value < p->value)  //寻找放入哈希表的位置
		q = q->next;

	p->next = q->next;  //放入哈希表
	q->next = p;

	return 1;
}

(4)数据查询

/**
 * @description: 数据查询
 * @param {hash} *HT-哈希表指针
 * @param {data_t} value-查询的数据
 * @return {返回数据所在哈希表的位置}
 */
linkhash query_hash(hash *HT, data_t value)
{
	int key = value % N;
	linkhash p;
	if(HT == NULL)
	{
		#if DEBUG
		printf("HT is NULL!\n");
		#endif
		return 0;
	}

	p = &(HT->a[key]);
	while(p)
	{
		if(p->value == value)
			return p;
		p = p->next;
	}
	return NULL;
}

二、排序

1、排序的定义

(1)稳定排序和非稳定排序

设文件f=(R1……Ri……Rj……Rn)中记录Ri、Rj(i≠j,i、j=1……n)key相等,即Ki=Kj
若在排序前Ri领先于Rj,排序后Ri仍领先于Rj,则称这种排序是稳定的,其含义是它没有破坏原本已有序的次序。反之就是非稳定排序。

(2)内排序和外排序

待排文件f在计算机的内存储器中,且排序过程也在内存中进行,称这种排序为内排序。`

排序中的文件存入外存储器,排序过程借助于内外存数据交换(或归并)来完成,则称这种排序为外排序


2、排序的方法

各种内排序方法可归纳为以下五类:
(1)插入排序
(2)交换排序
(3)选择排序
(4)归并排序
(5)其他
网上方法太多太多了,这里主要给出效率较高的选择排序法的实现。


3、快速排序法

设记录的key集合k={50,36,66,76,36,12,25,95},每次以集合中第一个key为基准的快速排序过程如下:
在这里插入图片描述
实现程序为:

#include<stdio.h>
#include<stdlib.h>

#define N 15

int query_sort(int *a, int low, int high);
int sort(int *a, int low, int high);
int compare(const void *a, const void *b);

int main(int argc,const char *argv[])
{
 	int i,a[N] = {0};

	srandom(10);

	for(i = 0; i < N; i++)
		a[i] = random() % 100;

	for(i = 0; i < N; i++)
		printf("%d ",a[i]);
	putchar('\n');

	//qsort(a, N, sizeof(int), compare); //C库自带的排序函数,也是用的哈希表排序
	query_sort(a, 0, N-1);

	for(i = 0; i < N; i++)
		printf("%d ",a[i]);
	putchar('\n');

	return 0;
}

/**
 * @description: 一次排序确定中性值
 * @param {int} *a -要排序的数组头指针
 * @param {int} low -低位
 * @param {int} high -高位
 * @return {*} low -调整后的中性值位置
 */
int sort(int *a, int low, int high)
{
	int temp;
	if(a == NULL)
	{	
		printf("array is NULL!\n");
		return 0;
	}

	temp = a[low];
	while(low < high)
	{
		while(low < high)
		{
			if(temp > a[high])
			{
				a[low] = a[high];
				break;
			}
			high--;
		}
		while(low < high)
		{
			if(temp < a[low])
			{
				a[high] = a[low]; 		
				break;
			}
			low++;
		}

	}
	a[low] = temp;

	return low;
}

/**
 * @description: 哈希排序
 * @param {int} *a -数组头指针
 * @param {int} low -低位
 * @param {int} high -高位
 * @return {*} -1-函数失败,0或1-函数成功
 */
int query_sort(int *a, int low, int high)
{
	int r = 0;
	if(a == NULL)
	{
		printf("array is NULL!\n");
		return -1;
	}

	if(low >= high)
		return 0;
	
	r = sort(a, low, high);
	query_sort(a, low, r-1); //比中性值小的
	query_sort(a, r+1, high); //比中性值大的

	return 1;
}

//比较函数
int compare(const void *a, const void *b)
{
	return *(const int *)a - *(const int *)b;
}

到这里就结束啦!
最后还是给出哈希表与选择排序的实现程序的链接:
哈希表与选择排序C程序实现


在这里插入图片描述

  • 5
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
内容简介: 无论你是从事业务开发,还是从事架构设计,想要优化设计模式,数据结构与算法是必备的一门学科,本课程使用Java来讲解数据结构算法,考虑到数据结构算法较难,授课采用图解加算法游戏的方式。内容包括: 稀疏数组、单向队列、环形队列、单向链表、双向链表、环形链表、约瑟夫问题、栈、前缀、中缀、后缀表达式、中缀表达式转换为后缀表达式、递归与回溯、迷宫问题、八皇后问题、算法的时间复杂度、冒泡排序、选择排序、插入排序、快速排序、归并排序、希尔排序、基数排序(桶排序)、堆排序排序速度分析、二分查找、插值查找、斐波那契查找、散列、哈希表、二叉树、二叉树与数组转换、二叉排序树(BST)、AVL树、线索二叉树、赫夫曼树、赫夫曼编码、多路查找树(B树B+树和B*树)、图、图的DFS算法和BFS、程序员常用10大算法、二分查找算法(非递归)、分治算法、动态规划算法、KMP算法、贪心算法、普里姆算法、克鲁斯卡尔算法、迪杰斯特拉算法、弗洛伊德算法马踏棋盘算法。为什么学数据结构与算法算法是一个程序员真正的核心竞争力。无论用哪种语言做开发,算法程序角度而言都是灵魂内核般的存在。程序的躯体可以各式各样,但是内核一定要追求高效整洁。同时掌握了算法,大厂名企的Offer不再是梦寐以求的梦想,而让程序高效且健壮,也不再是难以完成的技术难题。所以无论是为提升自我内功修炼,还是提升程序灵魂内核健全,学习算法,都是现有可供选项里的最优解。课程大纲:为了让大家快速系统了解数据结构与算法知识全貌,我为你总结了「数据结构与算法框架图」,帮你梳理学习重点,建议收藏!! CSDN学院Java答疑群:
排序是计算机科学中常见的操作,它将一组元素按照特定的顺序重新排列。排序算法的目标通常是将元素按照升序或降序排列。 常见的排序算法有很多种,每种算法都有不同的时间复杂度和空间复杂度。以下是几种常见的排序算法: 1. 冒泡排序(Bubble Sort):比较相邻的两个元素,如果顺序不正确就交换位置,每次遍历将一个最大(最小)的元素移到最后(最前)。时间复杂度为O(n^2)。 2. 插入排序(Insertion Sort):将数组分为已排序和未排序两部分,每次从未排序部分取出一个元素,插入已排序部分的适当位置。时间复杂度为O(n^2)。 3. 选择排序(Selection Sort):每次从未排序部分选择一个最小(最大)的元素放到已排序部分的末尾。时间复杂度为O(n^2)。 4. 快速排序(Quick Sort):选取一个基准元素,将数组划分为两个子数组,小于基准元素的放在左边,大于基准元素的放在右边,然后对子数组进行递归排序。时间复杂度平均情况下为O(nlogn),最坏情况下为O(n^2)。 5. 归并排序(Merge Sort):将数组递归分成两个子数组,然后对子数组进行排序,最后将两个已排序的子数组合并成一个有序数组。时间复杂度为O(nlogn)。 6. 堆排序(Heap Sort):将数组构建成一个最大(最小)堆,每次从堆顶取出最大(最小)元素放到已排序部分的末尾,然后调整堆使其满足堆的性质。时间复杂度为O(nlogn)。 这里只介绍了几种常见的排序算法,每种算法都有其适用的场景和优缺点。在实际应用中,根据数据规模和性能要求选择合适的排序算法非常重要。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

修成真

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值