数据结构与算法 -- 算法

一、算法的基本概念

算法是指对解题方案准确而完整的描述,是一系列解决问题的清晰指令,算法代表着用系统的方法描述解决问题的策略机制。

算法中的指令描述的是一个计算过程,在它的作用下,系统从初始状态和初始输入(也可能没有)开始,经历一系列有限且被明确定义的中间状态,最终产生所要求的输出,并停止于终止状态。

同一个问题,不同的算法,可能会在时间、空间等方面表现出明显的差异,一个算法的优劣可以用时间复杂度和空间复杂度来衡量。

二、算法的基本特征

(1)有穷性
算法必须能在执行有限个步骤后终止
(2)确切性
算法的每一个步骤必须有确切的定义
(3)输入项
算法有规范化的输入,以表示运算对象的初始状态
(4)输出项
算法至少有一个输出,以反映处理的最终结果
(5)可行性
算法中的每个执行步骤都必须能在有限时间内完成

三、算法的基本要素

(1)运算和操作
计算机可以执行的基本操作是以指令的形式描述的,包括如下四类:
算数运算:加、减、乘、除、模等
关系运算:大于、小于、等于、不等于等
逻辑运算:与、或、非等运算
数据传输:输入、输出、赋值等
(2)流程和控制
算法的功能不仅取决于所选用的操作,还与各操作之间的执行顺序有关

四、算法的评定标准

(1)时间复杂度
算法的时间消耗与问题规模之间的函数关系:T(N) = O(F(N))
(2)空间复杂度
算法的空间消耗与问题规模之间的函数关系:S(N) = O(F(N))
(3)正确性
执行算法的结果是否满足要求
(4)可读性
算法本身可供人们阅读的难易程度
(5)健壮性
算法对非正常输入的反应和处理能力,亦称容错性

五、算法的思想

(1)递推法
通过计算前面的一些项来得出序列中指定项的值, 其思想是把一个复杂而庞大的计算过程转化为简单过程的多次重复,充分发挥计算机速度快且不知疲倦的特点
(2)递归法
把大问题转化为与原问题相似的小问题来求解, 递归的神奇之处在于用有限的语句来定义无线的对象集合。递归需要有终止条件,递归前进段和递归返回段,若终止条件不满足则递归前进,否则递归返回。
(3)穷举法
对于要解决的问题,列举出它所有的可能性,逐个判断其中哪些符合问题所要求满足的条件,从而得到问题的解。
(4)贪心算法
自顶向下,以迭代的方式做出相继的贪心选择,每做一次贪心选择,就将所求问题简化为一个规模更小的子问题,通过每一步贪心选择,可得到相应子问题的一个最优解,虽然每一步都能保证局部解最优,但最终得到的全局解未必最优。
(5)分治法
把一个复杂问题分成两个或更多仙童或相似的子问题,再把子问题分成更小的子问题。。。直到最后的子问题可以简单地直接求解,原问题的解即子问题解的分并。
(6)动态规划法
将原问题分解为相似的子问题,在求解的过程中通过子问题的解求出原问题的解。
(7)迭代法
按照一定的规则,不断地用旧值推算出新值,直到满足某种特定条件为止。利用计算机速度快、擅长简单重复的特点,让计算机重复执行若干步骤,并在每次执行这些步骤时,都从旧值算出新值。
(8)分支界限法
把全部可行的解孔家不断分割为越来越小的子集,谓之分支,并为每个子集计算一个下界或上界,谓之定界。在每次分支后,对界限超出已知可行解的子集不再做进一步分支,搜索范围迅速收敛。这一过程一直进行到找出可行解为止,该可行解的值不大于任何子集的界限,因此这种算法一般可以求得全局最优解。
(9)回朔法
在包含问题所有解的解空间树中,从根节点出发,按深度优先搜索解空间树,当搜索到某一节点时,先判断该节点是否包含问题的解,若包含则从节点出发继续搜索,否则逐层向其父节点回溯。若希望求得问题的所有解,则必须回溯到根,且根节点的所有可行子树都必须被搜索到才能结束,否则只要搜索到问题的一个解即可终止。

六、算法的分类

算法按其执行期限可被分为:
(1)有限算法
算法过程无论长短,总能在有限的时间内终止
(2)无限算法
算法过程因无终止条件或终止条件无法得到满足,而永不停息
算法按其解的确定性可被分为:
(1)确定性算法
对于确定的输入,算法总能得到确定的结果
(2)非确定算法
对于确定的输入,算法得到的结果并不唯一确定

七、常见的查找算法

1、线性查找(顺序查找)

(1)算法描述
从头开始,依次将每一个元素与查找目标进行比较,直到找到目标元素,或者与所有元素比较完毕,表示查找失败。
(2)算法评价
平均时间复杂度:O(N)
对数据的有序性没有要求
(3)线性查找实现
//线性查找算法的实现
#include <stdio.h>

int find (int arr[], int data, int num)
{
	int i = 0;
	for (i = 0; i < data; i++)
	{
		if (arr[i] == num)
		{
			return i;
		}
	}
}

int main (void)
{
	int arr[9] = {1, 5, 4, 3, 6, 8, 9, 2, 7};
	printf ("元素3所在的下标是:%d\n", find (arr, 9, 2));
	return 0;
}
输出结果:
元素2所在的下标是:7

2、二分查找

(1)算法描述
首先假设所有的元素按照升序排列,使用中间元素和目标元素进行比较,如果相等则查找成功,如果中间元素小于目标元素,则去中间元素的右侧查找,否则去中间元素的左侧查找,重复以上过程直到查找成功,或者查找失败。
(2)算法评价
平均时间复杂度:O(logN)
要求样本元素必须有序
(3)基于递归的二分查找实现
//基于递归的二分查找算法实现
#include <stdio.h>
int find (int arr[], int left, int right, int data)
{
  //表示向右缩进
	if (left <= right)
	{
		//1.计算中间元素的下标
		int p = (left + right) / 2;
		//2.使用中间元素和目标元素进行比较,如果相等,则直接返回下标
		if (arr[p] == data)
			return p;
		//3.如果中间元素小于目标元素,则去中间元素的右侧继续查找	
		else if (arr[p] < data)
			return find (arr, p+1, right, data);
		//4.如果中间元素大于目标元素,则去中间元素的左侧继续查找	
		else
			return find (arr, left, p - 1, data);
	}
	//5.比较完毕,查找失败
	else
	{
		printf ("查找失败\n");
		return -1;
	}
}

int main (void)
{
	int arr[9] = {11, 22, 33, 44, 55, 66, 77, 88, 99};
	//调用查找函数查找元素33,返回下标
	printf("元素33所在的下标是:%d\n",find(arr,0,8,33)); // 2
	printf("元素60所在的下标是:%d\n",find(arr,8,0,60)); // -1
	return 0;
}
输出结果:
元素33所在的下标是:2
查找失败
元素60所在的下标是:-1
(4)基于循环的二分查找实现
//基于循环的二分查找实现
#include <stdio.h>
int find (int arr[], int left, int right, int data)
{
	while (left <= right)
	{
		int p = (left + right) / 2;
		if (arr[p] == data)
			return p;
		else if (arr[p] > data)
			right = p - 1;
		else
			left = p + 1;
	}
	if (left > right)
	{
		printf ("查找失败\n");
		return -1;
	}
}

int main (void)
{
	int arr[9] = {11, 22, 33, 44, 55, 66, 77, 88, 99};
	//调用查找函数查找元素33,返回下标
	printf("元素33所在的下标是:%d\n",find(arr,0,8,33)); // 2
	printf("元素44所在的下标是:%d\n",find(arr,8,0,44)); // -1
	return 0;
}
输出结果:
元素33所在的下标是:2
查找失败
元素44所在的下标是:-1
(5)二分查找说明

八、常见的排序算法

1、冒泡排序算法

(1)算法描述
相邻元素两两比较,前者大于后者,彼此交换。
从第一对到最后一对,最大的元素沉降到最后。
针对未排序部分,重复以上步骤,沉降次大值。
每次扫描越来越少的元素,直至不再发生交换。
(2)算法评价
平均时间复杂度 O(N^2)
稳定排序,对数据的有序性非常敏感
(3)冒泡排序算法实现
//冒泡排序函数
#include <stdio.h>
void bubble (int arr[], int len)
{
	int i = 0, j = 0;
	//外层循环控制比较的轮数
	for (i = 1; i < len; i++)
	{
		// 内层循环控制对当前轮数的下标访问	
		for (j = 0; j < len - i; j++)
		{
		  //如果左边元素大于等于右边交换
			if (arr[j] >= arr[j + 1])
			{
			  //防止数据丢失,找个替身
				int tmp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = tmp;
			}
		}
	}
}

int main (void)
{
	int arr[9] = {20, 8, 25, 3, 15, 9, 30, 5, 22};

	bubble (arr, 9);
	//调用排寻算法进行排序
	//冒泡排序
	int i = 0;
	for (i = 0; i < 9; i++)
		printf ("%d ", arr[i]);
	printf ("\n");
	return 0;
}
输出结果:
3 5 8 9 15 20 22 25 30 
(4)冒泡排序说明

2、插入排序算法

(1)算法描述
首元素自然有序
取出下一个元素,对已排序序列,从后向前扫描
大于被取出元素者后移
小于等于被取出元素者,将被取出元素插入其后
重复步骤2,直至处理完所有元素
(2)算法评价
平均时间复杂度 O(N^2)
稳定排序,
对数据的有序性份额长敏感,但是赋值的次数比冒泡排序少,因此略优于冒泡排序算法。
(3)插入排序算法实现
//插入排序算法的实现
#include <stdio.h>
//将第一元素看作有序  //假定已经有序,依次比较
void insert (int arr[], int len)
{
	int i = 0, j = 0;
	//1.从第二个元素起,一次取出每一个元素
	//外层循环控制比较的轮数
	for (i = 1; i < len; i++)
	{
	  //保存取出的元素  第二个元素
		int temp = arr[i];
		//2.使用取出的元素和左边有序元素比较,如果左边元素大于取出元素,则右边元素右移
		for (j = i; j > 0; j--)
		{
			if (arr[j - 1] > temp)
				arr[j] = arr[j - 1];
			else
				break;
		}

		if (i != j)
			arr[j] = temp;
	}
}

int main (void)
{
	int arr[9] = {20, 8, 25, 3, 15, 9, 30, 5, 22};
	//调用排寻算法进行排序
	//插入排序
	insert (arr, 9);
	int i = 0;
	for (i = 0; i < 9; i++)
		printf ("%d ", arr[i]);
	printf ("\n");
	return 0;
}
输出结果:
3 5 8 9 15 20 22 25 30 
(4)插入排序说明

3、选择排序算法

(1)算法描述
在整个序列中寻找最小元素,与首元素交换
在剩余序列中寻找最小元素,与次元素交换
依次类推,知道剩余序列中仅包含一个元素
(2)算法评价
平均时间复杂度:O(N^2)
非稳定排序
对数据的有序性不敏感
交换次数少,优于冒泡排序
(3)选择排序算法实现
//选择排序算法的实现
#include <stdio.h>
//假定最小值与实际最小值
void choose (int arr[], int len)
{
	//1.从第一个元素起依次取出,记录下标
	int i = 0, j = 0;
	for (i = 0; i < len; i++)
	{
		int min = i;//最小值下标为0
		//2.使用记录的最小值与后续元素比较,如果后续元素小于记录的最小值,则重新记录下标
		for (j = i + 1; j < len; j++)
		{
			if (arr[j] < arr[min])//下标1 < 下标0
				min=j;//最小值下标变为 下标 1
		}
		//3.直到记录的最小值和后续元素比较完毕,则交换记录的最小值和最开始假定的最小值
		//一开是假定的最小值是这组数中最小的
		if (i != min)
		{
			int temp = arr[i];
			arr[i] = arr[min];
			arr[min] = temp;
		}
	}
}

int main (void)
{
	int arr[9] = {20, 8, 25, 3, 15, 9, 30, 5, 22};
	//调用排寻算法进行排序
	//选择排序算法
	choose (arr, 9);
	int i = 0;
	for (i = 0; i < 9; i++)
		printf ("%d ", arr[i]);
	printf ("\n");
	return 0;
}
输出结果:
3 5 8 9 15 20 22 25 30 
(4)选择排序说明

4、快速排序算法

(1)算法描述
从待排序序列中任意挑选一个元素,作为基准
将所有小于基准的元素放在基准之前,大于基准的元素放在基准之后,等于基准的元素放在基准之前或之后,这个过程称为分组
以递归的方式,分别对基准之前和基准之后的分组继续进行分组,知道每个分组内的元素个数不多于 1 个为止
(2)算法评价
平均时间复杂度:O(NlogN)
非稳定排序
若每次都能均匀分组,则排序速度最快
(3)快速排序算法实现
//快速排序算法的实现
#include <stdio.h>
//选好基准
void quick (int arr[], int left, int right)
{
	//1.根据左边元素和右边元素下标计算出中间元素的下标
	int p = (left + right) / 2;
	//2.选择中间元素作为基准值,保存
	int pivot = arr[p];
	//3.分别使用左右来年改变的元素与基准值进行比较
	int i = 0, j = 0;
	for(i = left, j = right; i < j;)//找一个替身
	{
		//首先使用左边元素与基准值比较,如果左边元素小于基准值则比较下一个元素
		while (arr[i] < pivot && i < p)
			i++;
		//如果左边元素大于基准值,则将左边元素赋值给p指向的位置,p指向数据来源的位置
		if (i < p)
		{
			arr[p] = arr[i];
			p = i;//基准值不可赋值
		}
		//右边元素方法同上
		while(arr[j] >= pivot && j > p)
			j--;
		if (j > p)
		{
			arr[p] = arr[j];
			p = j;
		}
		//当i和j相等时,将基准值放到重合的位置上
		arr[p] = pivot;
		//分别对左边元素和右边元素进行递归排序
		if(p - left > 1)
			quick (arr, left, p - 1);
		if (right - p > 1)
			quick (arr, p + 1, right);
	}
}
int main()
{
	int arr[9] = {20, 8, 25, 3, 15, 9, 30, 5, 22};
	//调用排寻算法进行排序
	//快速排序算法
	quick (arr, 0, 8);
	int i = 0;
	for(i = 0; i < 9; i++)
		printf ("%d ", arr[i]);
	printf ("\n");
	return 0;
}
输出结果:
3 5 8 9 15 20 22 25 30 
(4)快速排序说明

5、归并排序算法

(1)算法描述
将待排序序列从中间划分为两个相等的子序列
以递归的方式分别对两个子序列进行排序
将两个有序的子序列合并成完整的序列

有序合并:
分配合并序列,其大小为两个有序序列大小之和
设定两个指针,分别指向两个有序序列的首元素
比较指针目标,娇笑着进入合并序列,指针后移
重复步骤3,直到某一指针到达序列末尾
将另一序列的剩余元素直接复制到合并序列末尾
(2)算法评价
平均时间复杂度:O(2NlogN)
稳定排序
对数据的有序性不敏感
非就地排序,需要辅助空间,不适合处理海量数据
(3)归并排序算法实现
#include<stdlib.h>
#include<stdio.h>

#define SIZE 8

void Merge (int sourceArr[], int startIndex, int midIndex, int endIndex)
{
    int start = startIndex;
    int i, j, k;
    int tempArr[SIZE];
    for (i = midIndex + 1, j = startIndex; startIndex <= midIndex && i <= endIndex; j++)
        if (sourceArr[startIndex] <= sourceArr[i])
            tempArr[j] = sourceArr[startIndex++];
        else
            tempArr[j] = sourceArr[i++];
    if (startIndex <= midIndex)
        for (k = 0; k <= midIndex - startIndex; k++)
            tempArr[j + k] = sourceArr[startIndex + k];
    if (i <= endIndex)
        for (k = 0; k <= endIndex - i; k++)
            tempArr[j + k] = sourceArr[i + k];
    for (i = start; i <= endIndex; i++)
        sourceArr[i] = tempArr[i];
}

//内部使用递归
void MergeSort (int sourceArr[], int startIndex, int endIndex)
{
    int midIndex;
    if (startIndex < endIndex)
    {
        midIndex = (startIndex + endIndex) / 2;
        MergeSort (sourceArr, startIndex, midIndex);
        MergeSort (sourceArr, midIndex+1, endIndex);
        Merge (sourceArr, startIndex, midIndex, endIndex);
    }
}

//调用
int main (void)
{
    int a[SIZE] = {8, 4, 6, 3, 1, 7, 5, 2};
    MergeSort (a, 0, SIZE-1);
	int i = 0;
    for (i = 0; i < SIZE; i++)
        printf ("%d ", a[i]);
    printf ("\n");
    return 0;
}
输出结果:
1 2 3 4 5 6 7 8 
(4)归并排序说明

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 猿与汪的秘密 设计师:上身试试 返回首页
实付 19.90元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值