顺序查找
顺序查找-又称线性查找,通常用于线性表。
算法思想:从头到尾进行扫描。
时间复杂度:O(n)
代码实现:
#include "stdio.h"
#define N 10 //待查找序列的元素个数
//实现顺序查找,arr[N] 为待查找序列,value 为要查找的目标元素
int linear_search(int arr[N], int value) {
int i;
//从第 1 个元素开始遍历
for (i = 0; i < N; i++) {
//匹配成功,返回元素所处的位置下标
if (arr[i] == value) {
return i;
}
}
//匹配失败,返回 -1
return -1;
}
int main()
{
int arr[N] = { 10,14,19,26,27,31,33,35,42,44 };
int add = linear_search(arr, 33);
if (add != -1) {
printf("查找成功,为序列中第 %d 个元素", add + 1);
}
else {
printf("查找失败");
}
return 0;
}
折半查找
折半查找-又称二分查找,仅适用于有序的顺序表。
算法思想:不断地缩小搜索区域,降低查找目标元素的难度。
时间复杂度:O(
log
2
n
\log_2n
log2n)
实现思路:
1.初始状态下,将整个序列作为搜索区域(假设为 [B, E]);
2.找到搜索区域内的中间元素(假设所在位置为 M),和目标元素进行比对。如果相等,则搜索成功;如果中间元素大于目标元素,表明目标元素位于中间元素的左侧,将 [B, M-1] 作为新的搜素区域;反之,若中间元素小于目标元素,表明目标元素位于中间元素的右侧,将 [M+1, E] 作为新的搜素区域;
3.重复执行第二步,直至找到目标元素。如果搜索区域无法再缩小,且区域内不包含任何元素,表明整个序列中没有目标元素,查找失败。
//实现二分查找算法,ele 表示要查找的目标元素,[p,q] 指定查找区域
int binary_search(int *arr,int p,int q,int ele) {
int mid = 0;
//如果[p,q] 不存在,返回 -1
if (p > q) {
return -1;
}
// 找到中间元素所在的位置
mid = p + (q - p) / 2;
//递归的出口
if (ele == arr[mid]) {
return mid;
}
//比较 ele 和 arr[mid] 的值,缩小 ele 可能存在的区域
if (ele < arr[mid]) {
//新的搜索区域为 [p,mid-1]
return binary_search(arr, p, mid - 1, ele);
}
else {
//新的搜索区域为 [mid+1,q]
return binary_search(arr, mid + 1, q, ele);
}
}
int main()
{
int a2[10] = { 10,14,19,26,27,31,33,35,42,44 };
//输出二叉查找元素 31 所在位置的下标
printf("%d", binary_search(a2, 0, 9, 31));
}
分块查找
算法思想:需要查找表本身之外,还需要根据查找表建立一个索引表。
分块有序指的是第二个子表中所有关键字都要大于第一个子表中的最大关键字,第三个子表的所有关键字都要大于第二个子表中的最大关键字,依次类推。
代码实现:
#include <stdio.h>
#include <stdlib.h>
struct index { //定义块的结构
int key;
int start;
} newIndex[3]; //定义结构体数组
int search(int key, int a[]);
int cmp(const void *a,const void* b){
return (*(struct index*)a).key>(*(struct index*)b).key?1:-1;
}
int main(){
int i, j=-1, k, key;
int a[] = {33,42,44,38,24,48, 22,12,13,8,9,20, 60,58,74,49,86,53};
//确认模块的起始值和最大值
for (i=0; i<3; i++) {
newIndex[i].start = j+1; //确定每个块范围的起始值
j += 6;
for (int k=newIndex[i].start; k<=j; k++) {
if (newIndex[i].key<a[k]) {
newIndex[i].key=a[k];
}
}
}
//对结构体按照 key 值进行排序
qsort(newIndex,3, sizeof(newIndex[0]), cmp);
//输入要查询的数,并调用函数进行查找
printf("请输入您想要查找的数:\n");
scanf("%d", &key);
k = search(key, a);
//输出查找的结果
if (k>0) {
printf("查找成功!您要找的数在数组中的位置是:%d\n",k+1);
}else{
printf("查找失败!您要找的数不在数组中。\n");
}
return 0;
}
int search(int key, int a[]){
int i, startValue;
i = 0;
while (i<3 && key>newIndex[i].key) { //确定在哪个块中,遍历每个块,确定key在哪个块中
i++;
}
if (i>=3) { //大于分得的块数,则返回0
return -1;
}
startValue = newIndex[i].start; //startValue等于块范围的起始值
while (startValue <= startValue+5 && a[startValue]!=key)
{
startValue++;
}
if (startValue>startValue+5) { //如果大于块范围的结束值,则说明没有要查找的数
return -1;
}
return startValue;
}
二叉排序树
特点:
二叉排序树中,如果根节点有左子树,那么左子树上所有节点的值都小于根节点。
二叉排序树中,如果根节点有右子树,那么右子树上所有节点的值都大于根节点。
代码实现:
#include<stdio.h>
#include<stdlib.h>
#define TRUE 1
#define FALSE 0
#define ElemType int
#define KeyType int
/* 二叉排序树的节点结构定义 */
typedef struct BiTNode
{
int data;
struct BiTNode *lchild, *rchild;
} BiTNode, *BiTree;
//二叉排序树查找算法
int SearchBST(BiTree T, KeyType key, BiTree f, BiTree *p) {
//如果 T 指针为空,说明查找失败,令 p 指针指向查找过程中最后一个叶子结点,并返回查找失败的信息
if (!T) {
*p = f;
return FALSE;
}
//如果相等,令 p 指针指向该关键字,并返回查找成功信息
else if (key == T->data) {
*p = T;
return TRUE;
}
//如果 key 值比 T 根结点的值小,则查找其左子树;反之,查找其右子树
else if (key < T->data) {
return SearchBST(T->lchild, key, T, p);
}
else {
return SearchBST(T->rchild, key, T, p);
}
}
int InsertBST(BiTree *T, ElemType e) {
BiTree p = NULL;
//如果查找不成功,需做插入操作
if (!SearchBST((*T), e, NULL, &p)) {
//初始化插入结点
BiTree s = (BiTree)malloc(sizeof(BiTNode));
s->data = e;
s->lchild = s->rchild = NULL;
//如果 p 为NULL,说明该二叉排序树为空树,此时插入的结点为整棵树的根结点
if (!p) {
*T = s;
}
//如果 p 不为 NULL,则 p 指向的为查找失败的最后一个叶子结点,只需要通过比较 p 和 e 的值确定 s 到底是 p 的左孩子还是右孩子
else if (e < p->data) {
p->lchild = s;
}
else {
p->rchild = s;
}
return TRUE;
}
//如果查找成功,不需要做插入操作,插入失败
return FALSE;
}
//删除函数
int Delete(BiTree *p)
{
BiTree q, s;
//情况 1,结点 p 本身为叶子结点,直接删除即可
if (!(*p)->lchild && !(*p)->rchild) {
*p = NULL;
}
else if (!(*p)->lchild) { //左子树为空,只需用结点 p 的右子树根结点代替结点 p 即可;
q = *p;
*p = (*p)->rchild;
free(q);
}
else if (!(*p)->rchild) {//右子树为空,只需用结点 p 的左子树根结点代替结点 p 即可;
q = *p;
*p = (*p)->lchild;//这里不是指针 *p 指向左子树,而是将左子树存储的结点的地址赋值给指针变量 p
free(q);
}
else {//左右子树均不为空,采用第 2 种方式
q = *p;
s = (*p)->lchild;
//遍历,找到结点 p 的直接前驱
while (s->rchild)
{
q = s;
s = s->rchild;
}
//直接改变结点 p 的值
(*p)->data = s->data;
//判断结点 p 的左子树 s 是否有右子树,分为两种情况讨论
if (q != *p) {
q->rchild = s->lchild;//若有,则在删除直接前驱结点的同时,令前驱的左孩子结点改为 q 指向结点的孩子结点
}
else {
q->lchild = s->lchild;//否则,直接将左子树上移即可
}
free(s);
}
return TRUE;
}
int DeleteBST(BiTree *T, int key)
{
if (!(*T)) {//不存在关键字等于key的数据元素
return FALSE;
}
else
{
if (key == (*T)->data) {
Delete(T);
return TRUE;
}
else if (key < (*T)->data) {
//使用递归的方式
return DeleteBST(&(*T)->lchild, key);
}
else {
return DeleteBST(&(*T)->rchild, key);
}
}
}
void order(BiTree t)//中序输出
{
if (t == NULL) {
return;
}
order(t->lchild);
printf("%d ", t->data);
order(t->rchild);
}
int main()
{
int i;
int a[5] = { 3,4,2,5,9 };
BiTree T = NULL;
for (i = 0; i < 5; i++) {
InsertBST(&T, a[i]);
}
printf("中序遍历二叉排序树:\n");
order(T);
printf("\n");
printf("删除3后,中序遍历二叉排序树:\n");
DeleteBST(&T, 3);
order(T);
}
平衡二叉树(AVL)
特点:
每棵子树中的左子树和右子树的深度差不超过1。
二叉树中每棵子树都要求是平衡二叉树。
平衡因子:每个结点都有其各自的平衡因子,表示的就是其左子树深度同右子树深度的差。平衡二叉树中各结点平衡因子的取值只可能是:0、1 和 -1。
二叉树插入转换为平衡二叉树:
LL:单侧右旋:
RR:单侧左旋:
LR:先左旋在右旋:(CR与new都可作为新插入点)
RL:先右旋在左旋:(CR与new都可作为新插入点)
红黑树
本身是一颗二叉查找树,在其基础上附加俩个要求:
1.树中的每个结点增加了一个用于存储颜色的标志域;
2.树中没有一条路径比其他任何路径长出两倍,整棵树要接近于“平衡”的状态
ps:这里所指的路径,指的是从任何一个结点开始,一直到其子孙的叶子结点的长度;接近于平衡:红黑树并不是平衡二叉树,只是由于对各路径的长度之差有限制,所以近似于平衡的状态。
红黑树特点
1.树中的每个结点颜色不是红的,就是黑的;
2.根结点的颜色是黑的;
3.所有为 nil 的叶子结点的颜色是黑的;(注意:叶子结点说的只是为空(nil 或 NULL)的叶子结点!)
4.如果此结点是红的,那么它的两个孩子结点全部都是黑的;
5.对于每个结点,从该结点到到该结点的所有子孙结点的所有路径上包含有相同数目的黑结点;