常见算法及问题需注意的技巧与简单实现

1、合并两个有序数组A,B,并使合并后的数组在A中。(假设A中有足够的空间存储两个数组中的元素)
void mergeSortedArray(int A[],int m, int B[],int n)
{
    int index = n + m;//从后向前粘贴可以避免没有判断的元素被覆盖
    
    while(m>0 && n>0){
        if(A[m-1] > B[n-1]){
            A[--index] = A[--m];
        }
        else{
            A[--index] = B[--n];
        }
    }
    while(n>0){
       A[--index] = B[--n];
    }
    while(m>0){
       A[--index] = A[--m];
    }
}
1-1 写一个函数 void *memmove(void *dest, const void *src, size_t n) 将一个字符串数组移动到目的地址为起点的地址。
void *memmove(void *dest, const void *src, size_t n)
{
    char *p1 = dest;
    const char *p2 = src;
    //要考虑两个地址是否会有内存重叠的部分,不考虑会造成未移动的数据被覆盖而丢失
    if(p2 < p1)//若源地址比目的地址小;从源地址的最后逆序赋值可以避免未拷贝的数据被覆盖
    {
        p2 += n;
        p1 += n;
        while(n-- != 0)
            *--p1 = *--p2;
    }
    else{
        while(n-- != 0)
            *p1++ = *p2++;
    }
    return p1;
}

v = *p++; // * 和 ++ 同级,自右向左结合 等价于 v = *(p++)

​ // 先取p所指目标变量的值,赋予变量v; 再对 p 对进行增1,即指针p指向下一个目标变量。

v = (*p)++; // 先将变量 *p 的值赋予v,再对变量 *p进行增1

2、链表要删除当前所指的节点,而不是当前指针的下一个节点:

技巧是:把当前节点的值用next的值覆盖,然后删除next节点

​ 相当于删除当前节点,实际是删除了next节点而将next的值拿来覆盖了当前节点的值。

3、埃拉托色尼(The Sieve of Eratosthenes) 质数筛选法(常考点)

​ 算法的核心思想:先将范围内的数都假设为质数,然后从最小的质数开始,将质数的倍数更改为非质数。

//假设求2-200之间的质数
#include<stdio.h>
#include<string.h>
#define n 200
int main()
{
    int flag[n+1];
    memset(flag,1,sizeof(flag));//定义在string.h中
    flag[0] = flag[1] = 0;
    for(int i=2; i<n; ++i)
    {
        if(flag[i]){
            for(int j=2; i*j<n; ++j){//将质数的倍数都置为0
                flag[i*j] = 0;
            }
        }
    }
    int cnt = 0;//记录质数个数
    for(int i=2; i<n; ++i){
        if(flag[i]){
            ++cnt;
            printf("%d ",i);
        }
    }
    printf("\n 质数个数为:\n",cnt);
    return 0;
}

算法的双层循环处还可以优化以提高执行效率

//假设求2-200之间的质数
#include<stdio.h>
#include<string.h>
#define n 200
int main()
{
    int flag[n+1];
    memset(flag,1,sizeof(flag));//定义在string.h中
    flag[0] = flag[1] = 0;
    for(int i=2; i<sqrt(n); ++i)//外层循环结束条件到sqrt(n)就可以 判断条件也可以写为 i*i<n
    //一个合数n必有一个不大于sqrt(n)的正因子,故一个数若是没有小于sqrt(n)的正因子,则说明它是一个素数
    {
        if(flag[i]){
            for(int j=i*i; j<n; j+=i){//将质数的倍数都置为0。内层循环从i*i开始即可,之前的已经判断过
                flag[j] = 0;
            }
        }
    }
    int cnt = 0;//记录质数个数
    for(int i=2; i<n; ++i){
        if(flag[i]){
            ++cnt;
            printf("%d ",i);
        }
    }
    printf("\n 质数个数为:\n",cnt);
    return 0;
}
4、输入一行字符串,统计其中包括多少单词,单词之间用空格分隔。
#include<stdio.h> 
#include<string.h> 
main() 
{  
    char str[200];//定义字符数组,存储字符串  
    int i;  
    int space=0;//空格标志,0 表示新空格,1 表示连续空格  
    int num=0;//单词数量    
    printf("请输入字符串:");  
    gets(str);  
    if(str[0]==' ')//去掉第一行开头的空格   
        space=1;  
    for(i=0;str[i]!='\0';i++)// 这个循环很关键,里边的判断很巧妙    
    {   
        if(str[i]==' ')//处理连续空格的情况,当前字符为空格   
        {    
            if(space==0)//新空格    
            {     
                space=1;//表示连续空格     
                num=num+1;    
            }   
        }   
        else    
            space=0;// 新空格     
    }  
    if(space==0)//如果字符串不以空格结束,则单词数增 1 
        num=num+1;  
    printf("单词总数为: %d\n",num);//输出结果 
} 

程序分析:本题容易出错的地方主要是对字符串前后空格的判断。

5、反转一个单链表(reverse linked list)

递归法

struct ListNode {
    int val;
    struct ListNode *next;
};
 
//递归反转整个链表
struct ListNode* reverse(struct ListNode* head) {
    if (head->next == null) return head;
    struct ListNode* last = reverse(head->next);
    head->next->next = head;//将下一个节点变成当前节点的前驱节点
    head->next = null;
    return last;
}

非递归法(迭代法)

typedef struct ListNode {
    int val;
    struct ListNode *next;
}ListNode;
ListNode reverseList(ListNode head)
{
    ListNode pre = NULL;
    while(head != NULL)
    {
        ListNode cur = head;
        head = head->next;
        cur->next = pre;
        pre = cur;
    }
    return pre;
}
6、编写一个函数,利用递归方法找出一个数组中的最大值和最小值,要求递归调用函数的格式如下:

MinMaxValue(arr,n,&max,&min),其中arr是给定的数组,n是数组的个数,max、min分别是最大值和最小值。

void MinMaxValue(int arr[],int n, int* max, int* min)//这里的man和min都是出参
{
    if(n==1)
    {
        *max = arr[0];
        *min = arr[0];
    }
    else
    {
        int tmp_max = arr[0];//因为arr+1操作,这里在每次递归中都是变化的
        int tmp_min = arr[0];
        MinMaxValue(arr+1, n-1, max, min);
        if(*max < tmp_max) *max = tmp_max;
        if(*min > tmp_min) *min = tmp_min;
    }
}
7、一个C语言程序的编译到运行的过程

在这里插入图片描述

预处理:用于将所有的#include头文件以及宏定义替换成其真正的内容,预处理之后得到的仍然是文本文件,但文件体积会大很多。

编译:这里的编译不是指程序从源文件到二进制程序的全部过程,而是指将经过预处理之后的程序转换成特定汇编代码(assembly code)的过程。

汇编:将上一步的汇编代码转换成机器码(machine code),这一步产生的文件叫做目标文件,是二进制格式。这一步会为每一个源文件产生一个目标文件。

链接:将多个目标文以及所需的**库文件(.so等)**链接成最终的可执行文件(executable file)。

8、内联函数

调用内联函数时,编译器首先检查调用是否正确(类型安全检查或者自动进行类型转换)。

如果正确,则将内联函数的代码直接替换函数调用,并且用实参换形参,于是省去了函数调用的开销

因此,内联机制增加了空间开销而节约了时间开销。(空间换时间)

内联函数与用 #define 命令实现的带参宏定义有些相似,但不完全相同:

用内联函数可以达到用 #define 宏置换的目的,但不会出现 带参宏定义 的副作用: 如自增运算时,容易出现错误,因为宏是直接替换参数的,比如:

#define square(a) (a)*(a)
int  a = 1;
int re = square(a++);
// 可能 a = 3  re = 2
// 因为编译器可能以不同的方式对表达式((a++)*(a++)进行求值

慎用内联函数

1)使用内联函数可以节省运行时间,但却增加了目标程序的长度

2)函数体内出现循环或递归等复杂的结构控制语句时,s不适合定义为内联函数

3)一个好的编译器将会根据函数的函数体,自动取消不值得的内联

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值