查找与排序

1.重要的排序算法
总共11种排序
在这里插入图片描述
但以下的排序用的较多,需要掌握,手撕代码

希尔排序

一种充分利用插入排序的特点的排序
1.元素少,排序效率高
2.数组接近有序效率高

流程

1.先取增量(增量递减)
2.根据增量分组
样本N个进行,根据gap分成多组 一层for循环
a[0],a[0+gap],a[0+2gap] +…
a[1],a[1+gap],a[1+2gap] +…
a[2],a[2+gap]
3.对每组进行,组内使用插入排序(两层for)
4.重复上述步骤 直到gap=1 (gap=1其实就是完全的插入排序)
详细见下图
一般gap首先取 长度/2 12/2=6
在这里插入图片描述
在这里插入图片描述

实现代码

void shellSort(int *data, int length)
{
    // 分组每次取一半,最后到两重复上述步骤 直到gap=1 (gap=1其实就是完全的插入排序)
    //3 ... 1 ... 4一定是 3 1和先比较 后面 3 4 再比较
    for (int gap = length / 2; gap >= 1; gap /= 2)
    {
        for (int i = gap; i < length; i++)
        {
            int temp = data[i]; //保存当前需要调整的值
            int j = i - gap;    //取组内的前一个元素
            // 每组遍历
            while (j >= 0 && temp < data[j])
            {
                //组内排序 类似插入一样,前面有序整体后移
                data[j + gap] = data[j];
                j = j - gap;
            }
            //将temp插入合适位置
            data[j + gap] = temp;
        }
    }
}

快速排序

快速排序算法首先会在序列中随机选择一个基准值,然后将除了基准值以外的数分
为“比基准值小的数”和“比基准值大的数”这两个类别,再将其排列成以下形式。
[ 比基准值小的数] 基准值 [ 比基准值大的数]
接着,对两个“[ ]”中的数据进行排序之后,整体的排序便完成了。
对“[ ]”里面的数据 进行排序时同样也会使用快速排序

流程

1.设置基准值(第一个值开始)设置i和j
2.从后面第一个值j开始找,找到第一个比data[i]小的数值 16,进行调换(可以先把哨兵保存temp中)

在这里插入图片描述
3.再从前面开始找 data[i++],找比data[j]=23数大的第一个数进行交换 64
在这里插入图片描述
4.重复23步骤 直到 i==j,哨兵归位
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
5.对两个集合进行递归执行 重复上述步骤

实现代码

int sort(int *data, int left, int right) { 
	//每一次递归, 每调用一次, 确定一个值得正确位置
    if (left >= right) 
    	return 0;
    int i = left;
    int j = right;
    int key = data[left];
	
    while (i < j) {
    	 //从右边找到第一个小于哨兵的值
        while (i < j && key < data[j]) { // 
            j --;
        }
        data[i] = data[j];
        while (i < j && key >= data[i]) {
            i ++;
        }
        data[j] = data[i];
    }
    // i == j
    data[i] = key;
    //
    sort(data, left, i-1);
    sort(data, i+1, right);
}
void quicksort2(int a[], int left, int right)
{
	int i, j, t, temp;
	i = left;
	j = right;
	temp = a[(i + j) / 2];//基数取中
	while (i <= j)
	{
		while (a[j] > temp)//找到比基数小时j停止
			j--;
		while (a[i] < temp)//找到比基数大时i停止
			i++;
		if (i <= j)//满足条件交换
		{
			t = a[i];
			a[i] = a[j];
			a[j] = t;
			i++;
			j--;
		}
	}
	if (left<j)//j在left右边,需要处理基数左边的序列
		quicksort2(a, left, j);
	if (i<right)//i在right左边,需要处理基数右边的序列
		quicksort2(a, i, right);
}
int main() {
    int i = 0;
    int data[DATA_ARRAY_LENGTH] = {23, 64, 24, 12, 9, 16, 53, 57, 71, 79, 87, 97};
#if 0
    shell_sort(data, DATA_ARRAY_LENGTH);
#else
  	quick_sort(data, DATA_ARRAY_LENGTH);
#endif 
    for (i = 0;i < DATA_ARRAY_LENGTH;i ++) {
        printf("%4d", data[i]);
    }
   	printf("\n");
}

归并排序

是利用归并的思想实现的排序方法,该算法采用经典的分治(divide-and-conquer)策略(分治法将问题分(divide)成一些小的问题然后递归求解,而治(conquer)的阶段则将分的阶段得到的各答案"修补"在一起,即分而治之)。
 归并排序是稳定排序,它也是一种十分高效的排序,能利用完全二叉树特性的排序一般性能都不会太差。java中Arrays.sort()采用了一种名为TimSort的排序算法,就是归并排序的优化版本。从上文的图中可看出,每次合并操作的平均时间复杂度为O(n),而完全二叉树的深度为|log2n|。总的平均时间复杂度为O(nlogn)。而且,归并排序的最好,最坏,平均时间复杂度均为O(nlogn)
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

实现代码

void mergeSort(int arr[], int left, int right, int temp[])
{
	if (left<right)
	{
		int mid = (left + right) / 2;
		mergeSort(arr, left, mid, temp);//左边归并排序,使得左子序列有序
		mergeSort(arr, mid + 1, right, temp);//右边归并排序,使得右子序列有序
		merge(arr, left, mid, right, temp);//将两个有序子数组合并操作
	}
}
void merge(int arr[], int left, int mid, int right, int temp[])
{
	int i = left;//左序列指针
	int j = mid + 1;//右序列指针
	int t = 0;//临时数组指针
	while (i <= mid && j <= right)
	{
		if (arr[i] <= arr[j])
			temp[t++] = arr[i++];
		else
			temp[t++] = arr[j++];
	}
	while (i <= mid) {//将左边剩余元素填充进temp中
		temp[t++] = arr[i++];
	}
	while (j <= right) {//将右序列剩余元素填充进temp中
		temp[t++] = arr[j++];
	}
	t = 0;
	//将temp中的元素全部拷贝到原数组中
	while (left <= right)
	{
		arr[left++] = temp[t++];
	}
}

冒泡排序

void bubbleSort(int b[],int len){
	for (int i = 0; i<len-1; i++){
		bool ok = true;
		for (int j = 0; j <len-i-1; j++)
		{
			if (b[j]>b[j+1])
			{
				ok = false;
				int tmp = b[j];
				b[j] = b[j+1];
				b[j] =tmp;
			}	
		}
		if(ok)
			break;
	}
}

KMP排序

1.基本概念
前缀:不包含最后一个字符
后缀:不包含第一个字符
最长公共前后缀:前缀和后缀共同的串

前缀后缀最长公共前后缀长度
axxx0
ababx0
abca,abc,bcx0
abcaa,ab,abca,ca,bcaa1
abcaba,ab,abcb,ab,cab,bcabab2
abcabca,ab,abc,abca,abcabb,bc,abc,cabc,bcabcabc3

next数组

通过公共前后缀长度求出next数组,用于匹配失败时进行回溯
在这里插入图片描述

实现代码

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

// 
// shell , quick, kmp 
// pattern 
// 
// abcabc
// k = 0 前缀
// q = 1 后缀
void make_next(const char *pattern, int *next) {

    int q, k;
    int m = strlen(pattern);

    next[0] = 0; // 
    for (q = 1,k = 0;q < m; q ++) {

        while (k > 0 && pattern[q] != pattern[k]) {
            k = next[k-1];//难点 这里回退 
            //整个串里面有a b c三个子串a与b也有一段匹配求了存在在next中 b和c匹配一段
            //b和c匹配直到某个字符部匹配则回溯到a与b匹配则 a也与c前一段匹配
        }

        if (pattern[q] == pattern[k]) { // 如果前缀与后缀有相同的字符
            k ++;
        }
        next[q] = k;
    }
}


int kmp(const char *text, const char *pattern, int *next) {

    int n = strlen(text);
    int m = strlen(pattern);

    make_next(pattern, next);

    int i, q;
    for (i = 0, q = 0;i < n;i ++) { //i --> text, q --> pattern
#if 1
        while (q > 0 && pattern[q] != text[i]) {
            q = next[q-1];//q下标回溯 i下标不用回溯 优于暴力匹配
        }
#endif 
		
        if (pattern[q] == text[i]) {
            q ++;
        }
        // q == m --->  匹配成功
        if (q == m) {
            return i-q+1;
        }
    }
    return -1;
}


int main() {

    char *text = "abcabcabcabcabcd";
    char *pattern = "abcabcd";
    int next[20] = {0};

    int idx = kmp(text, pattern, next);
    printf("match pattern: %d\n", idx);

}

堆排序

//堆排序
class HeapSortSolution
{
    //对有一定顺序的堆,当前第i个结点取根左右的最大值(这个操作称heapfiy)
    void heapify(vector<int> &nums, int n, int i)
    {
        int l = i * 2 + 1, r = i * 2 + 2;
        int max = i;
        if (l < n && nums[l] > nums[max])
            max = l;
        if (r < n && nums[r] > nums[max])
            max = r;
        if (max != i)
        {
            swap(nums[max], nums[i]);
            //因为交换了,还需要向下调整
            heapify(nums, n, max);
        }
    }
    //建立大根堆
    void heapify_build(vector<int> &nums, int n)
    {
        //由最后一个结点下标是n-1,parent = (i-1)/2
        //从树的倒数第二层第一个结点开始,对每个结点进行heapify操作,然后向上走
        int temp = (n - 2) / 2;
        for (int i = temp; i >= 0; i--)
            heapify(nums, n, i);
        for (int i = 0; i < nums.size(); i++)
            cout << nums[i] << " ";
        cout << endl;
    }
    //数组 总共有n个结点
    void heapify_sort(vector<int> &nums, int n)
    {
        //建立大根堆
        heapify_build(nums, n);
        //每次交换最后一个结点和根节点(最大值),
        //对交换后的根节点继续进行heapify(此时堆的最后一位是最大值,因此不用管他,n变为n-1)
        for (int i = 0; i < n; i++)
        {
            swap(nums.front(), nums[n - i - 1]);
            heapify(nums, n - i - 1, 0);
        }
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值