C语言经典算法题-2

其他经典例题跳转链接

C语言经典算法-1
1.汉若塔 2. 费式数列 3. 巴斯卡三角形 4. 三色棋 5. 老鼠走迷官(一)6. 老鼠走迷官(二)7. 骑士走棋盘8. 八皇后9. 八枚银币10. 生命游戏

C语言经典算法-2
字串核对、双色、三色河内塔、背包问题(Knapsack Problem)、蒙地卡罗法求 PI、Eratosthenes筛选求质数

C语言经典算法-3
超长整数运算(大数运算)、长 PI、最大公因数、最小公倍数、因式分解、完美数、阿姆斯壮数

C语言经典算法-4
最大访客数、中序式转后序式(前序式)、后序式的运算、洗扑克牌(乱数排列)、Craps赌博游戏

C语言经典算法-5
约瑟夫问题(Josephus Problem)、排列组合、格雷码(Gray Code)、产生可能的集合、m元素集合的n个元素子集

C语言经典算法-6
数字拆解、得分排行,选择、插入、气泡排序、Shell 排序法 - 改良的插入排序、Shaker 排序法 - 改良的气泡排序

C语言经典算法-7
排序法 - 改良的选择排序、快速排序法(一)、快速排序法(二)、快速排序法(三)、合并排序法

C语言经典算法-8
基数排序法、循序搜寻法(使用卫兵)、二分搜寻法(搜寻原则的代表)、插补搜寻法、费氏搜寻法

C语言经典算法-9
稀疏矩阵、多维矩阵转一维矩阵、上三角、下三角、对称矩阵、奇数魔方阵、4N 魔方阵、2(2N+1) 魔方阵

11.字串核对

说明:今日的一些高阶程式语言对于字串的处理支援越来越强大(例如Java、Perl等),不过字串搜寻本身仍是个值得探讨的课题,在这边以Boyer- Moore法来说明如何进行字串说明,这个方法快且原理简洁易懂。
解法:字串搜寻本身不难,使用暴力法也可以求解,但如何快速搜寻字串就不简单了,传统的字串搜寻是从关键字与字串的开头开始比对,例如 Knuth-Morris-Pratt 演算法 字串搜寻,这个方法也不错,不过要花时间在公式计算上;Boyer-Moore字串核对改由关键字的后面开始核对字串,并制作前进表,如果比对不符合则依前进表中的值前进至下一个核对处,假设是p好了,然后比对字串中p-n+1至p的值是否与关键字相同。
如果关键字中有重复出现的字元,则前进值就会有两个以上的值,此时则取前进值较小的值,如此就不会跳过可能的位置,例如texture这个关键字,t的前进值应该取后面的3而不是取前面的7。


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

void table(char*); // 建立前进表 
int search(int, char*, char*); // 搜寻关键字 
void substring(char*, char*, int, int); // 取出子字串 

int skip[256]; 

int main(void) { 
char str_input[80]; 
char str_key[80]; 
char tmp[80] = {'\0'}; 
int m, n, p; 
printf("请输入字串:"); 
gets(str_input); 
printf("请输入搜寻关键字:"); 
gets(str_key); 
m = strlen(str_input); // 计算字串长度 
n = strlen(str_key); 
table(str_key); 
p = search(n-1, str_input, str_key); 

while(p != -1) { 
substring(str_input, tmp, p, m); 
printf("%s\n", tmp); 
p = search(p+n+1, str_input, str_key); 
} 

printf("\n"); 
return 0; 
} 

void table(char *key) { 
int k, n; 
n = strlen(key); 
for(k = 0; k <= 255; k++) 
skip[k] = n; 
for(k = 0; k < n - 1; k++) 
skip[key[k]] = n - k - 1; 
} 

int search(int p, char* input, char* key) { 
int i, m, n; 
char tmp[80] = {'\0'}; 
m = strlen(input); 
n = strlen(key); 

while(p < m) { 
substring(input, tmp, p-n+1, p); 
if(!strcmp(tmp, key)) // 比较两字串是否相同 
return p-n+1; 
p += skip[input[p]]; 
} 
return -1; 
} 

void substring(char *text, char* tmp, int s, int e) { 
int i, j; 
for(i = s, j = 0; i <= e; i++, j++) 
	mp[j] = text[i]; 
tmp[j] = '\0'; 
}

12.双色、三色河内塔

说明:双色河内塔与三色河内塔是由之前所介绍过的河内塔规则衍生而来,双色河内塔的目的是将下图左上的圆环位置经移动成为右下的圆环位置:

而三色河内塔则是将下图左上的圆环经移动成为右上的圆环:

解法:无论是双色河内塔或是三色河内塔,其解法观念与之前介绍过的河内塔是类似的,同样也是使用递回来解,不过这次递回解法的目的不同,我们先来看只有两个盘的情况,这很简单,只要将第一柱的黄色移动至第二柱,而接下来第一柱的蓝色移动至第三柱。
再来是四个盘的情况,首先必须用递回完成下图左上至右下的移动:

接下来最底层的就不用管它们了,因为它们已经就定位,只要再处理第一柱的上面两个盘子就可以了。那么六个盘的情况呢?一样!首先必须用递回完成下图左上至右下的移动:

接下来最底层的就不用管它们了,因为它们已经就定位,只要再处理第一柱上面的四个盘子就可以了,这又与之前只有四盘的情况相同,接下来您就知道该如何进行解题了,无论是八个盘、十个盘以上等,都是用这个观念来解题。
那么三色河内塔呢?一样,直接来看九个盘的情况,首先必须完成下图的移动结果:

接下来最底两层的就不用管它们了,因为它们已经就定位,只要再处理第一柱上面的三个盘子就可以了。

双色河内塔 C 实作 
#include <stdio.h>

void hanoi(int disks, char source, char temp, char target) {
    if (disks == 1) {
        printf("move disk from %c to %c\n", source, target);
        printf("move disk from %c to %c\n", source, target);
    } else {
        hanoi(disks-1, source, target, temp);
        hanoi(1, source, temp, target);
        hanoi(disks-1, temp, source, target);
    }
}

void hanoi2colors(int disks) {
    char source = 'A';
    char temp = 'B';
    char target = 'C';
    int i;
    for(i = disks / 2; i > 1; i--) {
        hanoi(i-1, source, temp, target);
        printf("move disk from %c to %c\n", source, temp);
        printf("move disk from %c to %c\n", source, temp);
        hanoi(i-1, target, temp, source);
        printf("move disk from %c to %c\n", temp, target);
    }
    printf("move disk from %c to %c\n", source, temp);
    printf("move disk from %c to %c\n", source, target);
}

int main() {
    int n;
    printf("请输入盘数:");
    scanf("%d", &n);

    hanoi2colors(n);

    return 0;
} 

三色河内塔 C 实作 
#include <stdio.h>

void hanoi(int disks, char source, char temp, char target) {
    if (disks == 1) {
        printf("move disk from %c to %c\n", source, target);
        printf("move disk from %c to %c\n", source, target);
        printf("move disk from %c to %c\n", source, target);
    } else {
        hanoi(disks-1, source, target, temp);
        hanoi(1, source, temp, target);
        hanoi(disks-1, temp, source, target);
    }
}

void hanoi3colors(int disks) {
    char source = 'A';
    char temp = 'B';
    char target = 'C';
    int i;
    if(disks == 3) {
        printf("move disk from %c to %c\n", source, temp);
        printf("move disk from %c to %c\n", source, temp);
        printf("move disk from %c to %c\n", source, target);
        printf("move disk from %c to %c\n", temp, target);
        printf("move disk from %c to %c\n", temp, source);
        printf("move disk from %c to %c\n", target, temp);;
    }
    else {
        hanoi(disks/3-1, source, temp, target);
        printf("move disk from %c to %c\n", source, temp);
        printf("move disk from %c to %c\n", source, temp);
        printf("move disk from %c to %c\n", source, temp);

        hanoi(disks/3-1, target, temp, source);
        printf("move disk from %c to %c\n", temp, target);
        printf("move disk from %c to %c\n", temp, target);
        printf("move disk from %c to %c\n", temp, target);

        hanoi(disks/3-1, source, target, temp);
        printf("move disk from %c to %c\n", target, source);
        printf("move disk from %c to %c\n", target, source);

        hanoi(disks/3-1, temp, source, target);
        printf("move disk from %c to %c\n", source, temp);
        
        for (i = disks / 3 - 1; i > 0; i--) {
            if (i>1) {
                hanoi(i-1, target, source, temp);
            }
            printf("move disk from %c to %c\n",target, source);
            printf("move disk from %c to %c\n",target, source);
            if (i>1) {
                hanoi(i-1, temp, source, target);
            }
            printf("move disk from %c to %c\n", source, temp);
        }
    }
}

int main() {
    int n;
    printf("请输入盘数:");
    scanf("%d", &n);

    hanoi3colors(n);
    return 0;
} 

13.背包问题(Knapsack Problem)

说明:假设有一个背包的负重最多可达8公斤,而希望在背包中装入负重范围内可得之总价物品,假设是水果好了,水果的编号、单价与重量如下所示:
0 李子 4KG NT$4500
1 苹果 5KG NT$5700
2 橘子 2KG NT$2250
3 草莓 1KG NT$1100
4 甜瓜 6KG NT$6700

解法:背包问题是关于最佳化的问题,要解最佳化问题可以使用「动态规划」(Dynamic programming),从空集合开始,每增加一个元素就先求出该阶段的最佳解,直到所有的元素加入至集合中,最后得到的就是最佳解。

以背包问题为例,我们使用两个阵列value与item,value表示目前的最佳解所得之总价,item表示最后一个放至背包的水果,假设有负重量 1~8的背包8个,并对每个背包求其最佳解。

逐步将水果放入背包中,并求该阶段的最佳解:

放入李子 背包负重 1 2 3 4 5 6 7 8 value 0 0 0 4500 4500 4500
4500 9000 item - - - 0 0 0 0 0

放入苹果 背包负重 1 2 3 4 5 6 7 8 value 0 0 0 4500 5700 5700
5700 9000 item - - - 0 1 1 1 0

放入橘子 背包负重 1 2 3 4 5 6 7 8 value 0 2250 2250 4500 5700
6750 7950 9000 item - 2 2 0 1 2 2 0

放入草莓 背包负重 1 2 3 4 5 6 7 8 value 1100 2250 3350 4500
5700 6800 7950 9050 item 3 2 3 0 1 3 2 3

放入甜瓜 背包负重 1 2 3 4 5 6 7 8 value 1100 2250 3350 4500
5700 6800 7950 9050 item 3 2 3 0 1 3 2 3

由最后一个表格,可以得知在背包负重8公斤时,最多可以装入9050元的水果,而最后一个装入的 水果是3号,也就是草莓,装入了草莓,背包只能再放入7公斤(8-1)的水果,所以必须看背包负重7公斤时的最佳解,最后一个放入的是2号,也就 是橘子,现在背包剩下负重量5公斤(7-2),所以看负重5公斤的最佳解,最后放入的是1号,也就是苹果,此时背包负重量剩下0公斤(5-5),无法 再放入水果,所以求出最佳解为放入草莓、橘子与苹果,而总价为9050元。

实作

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

#define LIMIT 8   // 重量限制 
#define N 5       // 物品种类 
#define MIN 1     // 最小重量 

struct body { 
    char name[20]; 
    int size; 
    int price; 
}; 

typedef struct body object; 

int main(void) { 
    int item[LIMIT+1] = {0}; 
    int value[LIMIT+1] = {0}; 
    int newvalue, i, s, p; 

    object a[] = {{"李子", 4, 4500}, 
                  {"苹果", 5, 5700}, 
                  {"橘子", 2, 2250}, 
                  {"草莓", 1, 1100}, 
                  {"甜瓜", 6, 6700}}; 

    for(i = 0; i < N; i++) { 
        for(s = a[i].size; s <= LIMIT; s++) { 
            p = s - a[i].size; 
            newvalue = value[p] + a[i].price; 
            if(newvalue > value[s]) {// 找到阶段最佳解 
                value[s] = newvalue; 
                item[s] = i; 
            } 
        } 
    } 

    printf("物品\t价格\n"); 
    for(i = LIMIT; i >= MIN; i = i - a[item[i]].size) { 
        printf("%s\t%d\n", 
                  a[item[i]].name, a[item[i]].price); 
    } 

    printf("合计\t%d\n", value[LIMIT]); 

    return 0; 
}  

14.蒙地卡罗法求 PI

说明:蒙地卡罗为摩洛哥王国之首都,该国位于法国与义大利国境,以赌博闻名。蒙地卡罗的基本原理为以乱数配合面积公式来进行解题,这种以机率来解题的方式带有赌博的意味,虽然在精确度上有所疑虑,但其解题的思考方向却是个值得学习的方式。
解法:蒙地卡罗的解法适用于与面积有关的题目,例如求PI值或椭圆面积,这边介绍如何求PI值;假设有一个圆半径为1,所以四分之一圆面积就为PI,而包括此四分之一圆的正方形面积就为1,如下图所示:

如果随意的在正方形中投射飞标(点)好了,则这些飞标(点)有些会落于四分之一圆内,假设所投射的飞标(点)有n点,在圆内的飞标(点)有c点,则依比例来算,就会得到上图中最后的公式。

至于如何判断所产生的点落于圆内,很简单,令乱数产生X与Y两个数值,如果X2+Y2等于1就是落在圆内。

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

#define N 50000 

int main(void) { 
    int i, sum = 0; 
    double x, y; 

    srand(time(NULL)); 

    for(i = 1; i < N; i++) { 
        x = (double) rand() / RAND_MAX; 
        y = (double) rand() / RAND_MAX; 
        if((x * x + y * y) < 1) 
            sum++; 
    } 
    printf("PI = %f\n", (double) 4 * sum / N); 
    return 0; 
} 

15.Eratosthenes筛选求质数

说明:除了自身之外,无法被其它整数整除的数称之为质数,要求质数很简单,但如何快速的求出质数则一直是程式设计人员与数学家努力的课题,在这边介绍一个着名的 Eratosthenes求质数方法。
解法:首先知道这个问题可以使用回圈来求解,将一个指定的数除以所有小于它的数,若可以整除就不是质数,然而如何减少回圈的检查次数?如何求出小于N的所有质数?

首先假设要检查的数是N好了,则事实上只要检查至N的开根号就可以了,道理很简单,假设AB = N,如果A大于N的开根号,则事实上在小于A之前的检查就可以先检查到B这个数可以整除N。不过在程式中使用开根号会精确度的问题,所以可以使用 ii <= N进行检查,且执行更快。

再来假设有一个筛子存放1~N,例如: 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
… N

先将2的倍数筛去: 2 3 5 7 9 11 13 15 17 19 21 … N

再将3的倍数筛去: 2 3 5 7 11 13 17 19 … N

再来将5的倍数筛去,再来将7的质数筛去,再来将11的倍数筛去…,如此进行到最后留下的数就都是质数,这就是Eratosthenes筛选方法(Eratosthenes
Sieve Method)。

检查的次数还可以再减少,事实上,只要检查6n+1与6n+5就可以了,也就是直接跳过2与3的倍数,使得程式中的if的检查动作可以减少。

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

#define N 1000 

int main(void) { 
    int i, j; 
    int prime[N+1]; 

    for(i = 2; i <= N; i++) 
        prime[i] = 1; 

    for(i = 2; i*i <= N; i++) { // 这边可以改进 
        if(prime[i] == 1) { 
            for(j = 2*i; j <= N; j++) { 
                if(j % i == 0) 
                    prime[j] = 0; 
            } 
        } 
    } 

    for(i = 2; i < N; i++) { 
        if(prime[i] == 1) { 
            printf("%4d ", i); 
            if(i % 16 == 0) 
                printf("\n"); 
        } 
    } 

    printf("\n"); 
    return 0; 
} 

系类好文
上一篇:
C语言经典算法-1
1.汉若塔 2. 费式数列 3. 巴斯卡三角形 4. 三色棋 5. 老鼠走迷官(一)6. 老鼠走迷官(二)7. 骑士走棋盘8. 八皇后9. 八枚银币10. 生命游戏

下一篇:
C语言经典算法-3
超长整数运算(大数运算)、长 PI、最大公因数、最小公倍数、因式分解、完美数、阿姆斯壮数

  • 22
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Token_w

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

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

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

打赏作者

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

抵扣说明:

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

余额充值