C语言典型习题案例2(链表逆序,排序(详析!),字符串逆序(详析!),长句转置,递归与非递归)

一、问题描述

1、原型:void *memcpy(void *dest, const void *src, size_t n);
功能:从源src所指的内存地址的起始位置开始拷贝n个字节到目标dest所指的内存地址的起始位置中;
memcpy没有考虑重叠的情况;

2、通过编程,实现字符串逆序

3、单链表逆序
功能:逆转链表,并返回逆转后的头结点,(给出单链表的结点结构)
typedef struct _node
{
     int data;
     struct _node * next;
}Node;
函数原型:Node* ReverseList(Node *head);

4、通过编程,求1-100以内的素数的个数;

5、通过编程,实现 I am from shanghai 逆序输出shanghai from am I;

6、用C写一个输入的整数,倒着输出整数的函数,要求用递归方法;

二、具体实现

1.do_memcpy()

首先我们来分析一下memcpy()出现的可能,他和strncpy()类似,strncpy()只能复制字符串,而memcpy()能够负责拷贝更高级的内存区域,比如说结构体等,所以memcpy()的入参是void *类型;但是有一点,一个指针所占的内存大小是4字节(在当代编译器编译后),而一个指向char类型的的大小是1字节,所以memcpy()拷贝的核心是以单位字节来拷贝的,所以实际内部我们可以将万用类型的指针强制转换成char *类型。因为万用类型void不能直接操作。

思路如下:

①. 首先利用sizeof操作符计算目标地址和原地址的内存大小(分别是 dlen 和 len)空间是多少字节 --->if(dlen < size)-->返回NULL;因为此时目标地址的空间比size的大小要小,所以往下拷贝一定会溢出。

②. 循环条件while(size--)

③. 将指针指向的单位内容从源端复制到目标端

④. 中间如果dlen的长度或者是len的长度提前为0那么就退出不再拷贝,因为随着指针偏移很有可能某一方会溢出。

#include <stdio.h>
#include <time.h>
#include <sys/types.h>
#include <signal.h>
#include <string.h>
#include <stdlib.h>

void *do_memcpy(void *dst, void *src, size_t size);

int main(int ac, char **av)
{
  while(--ac)
  {	
  	printf("now i proccess %s\n", *++av);
  	
  	if(*(av + 1) != NULL)
  		printf("The finally result is %s\n", (char *)do_memcpy(*(av), *(av+1), 2));
  }
  return 0;
  }
  
  void *do_memcpy(void *dst, void *src, size_t size)
  {
  size_t len = sizeof(src);
  size_t dlen = sizeof(dst);
  char *p = (char *)dst;//因为我最终要返回一个字符串的首地址,所以不能先偏移指针
  
  if(dlen < size)//如果目标端的大小比要拷贝的大小要小
  	return NULL; //直接退出
  
  while(size--)
  {
  	*p = *(char *)src;
  	p++;src++;
  
  	if(!len-- || !dlen--)//这里需要注意len要放在前面,如果len(源端)的长度提前结束(=0)
  		break;//那么就不能在拷贝了
  }
  
  return dst;
  }

实验结果:这里仍然以输入字符串为例子

2. 关于字符串逆序

关于字符串逆序,我这里总结了两种方法;一种是非递归法,一种是中间递归法。

①:首先介绍非递归法:

解决思路:如果只是想看看一个倒序的字符串是什么样子,可以直接使用指针带一个for()循环那就够了。这样的方式毫无意义。我们要对一个字符串处理,肯定是想让他变成我们想要的样子,而不是仅仅一个输出。那么改变一个字符串本身归根结底要从字符开始入手。

一串字符串是由若干个字符再在末尾处加上字符串结束标志构成的。

定义两个指向字符的指针,分别指向字符头(p)和字符的尾(q),让p向后移动的同时让q向前移动。每移动一次借助中间变量(常规算法),将两个字符交换,如若奇数个字符中间哪个则不处理,如若偶数个字符,就正好。

循环条件while(p < q)

实现如下:

void reverse_word(char *p, char *q)
{
  char tmp;
  while(p < q)//首与末
  {
  	tmp = *p; *p = *q; *q = tmp;
  	p++;q--;
  }
}

//这样我们就能将原来的字符串通过地址转换逆序了

 ②中间递归法

中间递归法比较复杂,因为在递归结束后,我们就要进行回归操作,没回归一次中间递归不向尾部递归,它每回归一级都要作后续的操作,所以没有一定的规律是写不了不了中间递归的。

编写中间递归的要点:首先我要知道中间递归要在哪里结束。结束完需要作哪些操作,因为函数总是要返回到被调用它的地方的。

首先传入一个字符串的首地址,计算其实际字符长度;实际长度则为字符串的个数。递归前的工作:将首个指针指向的内容保存到容器中,让首个字符和尾字符交换。并让最后一个字符用段落结束标记填充,这点很重要,也是中间递归停止递归的条件!!后续回归的时候根据不同的指针偏移,将ctemp填充到正确的位置,如此反复。

char *do_reverse(char *str)
{
  int len = strlen(str);
  if (len > 1) //判断递归结束的条件
  {
  	char ctemp = str[0];
  	str[0] = str[len - 1];
  	str[len - 1] = '\0';//最后一个字符在下次递归时不再处理
  	do_reverse(str + 1);//中间递归
  	str[len - 1] = ctemp;
  }
  
  return str;
}

这里我将最后一题:将一个整形数,利用递归的方式将其逆序合并在一个*.c文件中了,所以这里一并把整形变量逆序方式一并总结了:

输入一个整形数:(我的算法较为简单才学疏浅)123 将其逆序 ,要求改变其本身。首先我必须要知道什么时候递归结束,就是:其每个位的除数等于零的时候。

传入函数一个整形数,和其位权。位权默认初始值为0(必须为0), 位权值回归时不是最低位,而是最高位,开始回归。因为:

比如:198 = 1 * 10^2 + 9 * 10^1 + 8*10^0

程序如下:

int int_reverse(int val, int tmp)
{
	if(val == 0)
		return tmp;
	return int_reverse(val/10, tmp*10 + val%10);//直到除数为0时递归结束,开始以位权为十进制最高位权开始回归
}

整合代码:

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

//反转(逆置)字符串
char *do_reverse(char *str)
{
  int len = strlen(str);
  if (len > 1) 
  {
  	char ctemp = str[0];
  	str[0] = str[len - 1];
  	str[len - 1] = '\0';//最后一个字符在下次递归时不再处理
  	do_reverse(str + 1);//中间递归
  	str[len - 1] = ctemp;
  }
  
  return str;
}

int int_reverse(int val, int tmp)
{
  if(val == 0)
  	return tmp;
  return int_reverse(val/10, tmp*10 + val%10);
}

int main(int ac, char **av) 
{
  int ret;
  while(--ac)
  {
  	printf("now you process %s\n", *++av);
  	printf("reverse string is %s\n", do_reverse(*av));
  }
  printf("input  a number: ");
  scanf("%d", &ret);
  printf("Ret = %d\n", ret);
  printf("Ter = %d\n", int_reverse(ret, 0));
  
  return 0;
}

 运行结果如下:

递归分析:

以字符串123456789为例子

所谓中间递归,就是发生递归的位置在函数体的中间,而不是末尾。
尾递归在逐层退出时除了 return 语句,一般不再执行其他操作;而中间递归在逐层退出时还要执行一些其他的操作,所以比较复杂。

 3. 单链表逆序

代码:

#include <stdio.h>
#include <time.h>
#include <sys/types.h>
#include <signal.h>
#include <string.h>
#include <stdlib.h>

#define N 20

typedef struct list
{
	int num;
	struct list *next;
}Node, *Link;

int insert_tail(Link *head, Link new_node);//尾插
int judge_malloc(Link *node);//判断分配
void create_node(Link *new_node);
void create_link(Link *new_node);//创建链表基类型
int delete_node(Link *head, int loc);//指定删除
int insert_val(Link *head, Link new_node, int loc);//指定插入
Link sort_list(Link head);//排序
void display_list(Link head);//呈现
void clear_list(Link head);//清理
void release_list(Link *head);//释放
Link reverse_list(Link head);//转置链表
Link reverse_list2(Link head);


Link head;

static void handle(int signo)
{
	if(signo == 2)//SIGINT
	{
		printf("you'v received sigint signal advanced!\n");
		release_list(head);
		exit(-1);
	}
	if(signo == 11)//SIGSEGV
	{
		printf("you'v receive segment signal now!\n");
		release_list(head);
		exit(-1);
	}
}

int main(void)
{
    create_link(&head);
    int i, location;
    Link new_node;
    srand((unsigned)time(NULL));
    Link new_head, new_head2;
    
    signal(SIGINT, handle);
    signal(SIGSEGV, handle);
    
    for(i = 0; i < N; i++)
    {
    	create_node(&new_node);
    	new_node->num = rand()%20+1;
    	insert_tail(&head, new_node);
    }
    
    display_list(head);
    new_head = sort_list(head);
    display_list(new_head);
    
    create_node(&new_node);
    printf("input your own number:");
    scanf("%d", &new_node->num);
    printf("input your wanna insert location:");
    scanf("%d", &location);
    insert_val(&new_head, new_node, location);
    display_list(new_head);
    
    printf("input your wanna delete number:");
    scanf("%d", &location);
    delete_node(&new_head, location);
    display_list(new_head);
	
    printf("do once reverse for Link\n");
    new_head2 = reverse_list(new_head);
    display_list(new_head2);
	
    printf("do twice reverse for Link\n");
    display_list(reverse_list2(new_head2));
    
    release_list(&new_head);
    display_list(new_head);
    
    
    return 0;
}

int judge_malloc(Link *node)
{
	if(NULL == (*node))
		return 0;
	else
		return 1;
}

//创建单链表基类型
void create_link(Link *node)
{
	(*node) = NULL;
}

void create_node(Link *new_node)
{
	int i = N;
	do
	{
		(*new_node) = (Link)malloc(sizeof(Node));
		i--;
	}
	while (!judge_malloc(new_node) && i);	
}

int insert_tail(Link *head, Link new_node)
{
	Link p = *head;
	
	if((*head) == NULL)
	{
		(*head) = new_node;
		new_node->next = NULL;
	}
	else
	{
		while(p->next != NULL)
			p = p->next;

		p->next = new_node;
		new_node->next =NULL;
	}

	if(((*head)->next) != NULL)
		return 0;//插入成功
	else
		return 1;//插入失败
}

int insert_val(Link *head, Link new_node, int loc)
{
	Link p ,q;
    p=q=*head;
    if(NULL == p)
    {
        printf("no such node\n");
        free(new_node);
    }
    else
    {
        while(p->num!=loc && p->next!=NULL)
        {
            q=p;
            p=p->next;
        }
        if(p->next==NULL)
        {
            if(p->num!=loc)
            {
                printf("Thr location value to be inserted unexsitment!");  
                free(new_node);
            }
            else
            {
                if(p==*head)
                {
                    new_node->next=*head;
                    *head=new_node;
                }
                else
                {
                    q->next=new_node;
                    new_node->next=p;
                }
            }
        }
        else
        {
            if(p==*head)
            {
            	new_node->next=*head;
				*head=new_node;
            }
            else
            {
            	q->next=new_node;
				new_node->next=p;
            }            
        }
    }
}

void display_list(Link head)
{
	Link p;//创建临时结构体指针变量p 用于存放传过来的头节点地址
    p = head;//因为头不能变 所以要借助p偏移
    int count=0;
//判断头节点地址是不是空链表
    if(NULL == p)
    {
        printf("The linkList is Empty!\n");
    }
    else
    {
        while(p !=NULL)
        {
            printf("nums exsit in LinkList:%d\n",p->num);
            p = p->next;//移动指针
            count ++;
        }
        printf("一共有%d个结点\n",count);
    }
}

int delete_node(Link *head, int loc)
{
	Link p,q;
    p = q = *head;

    if(NULL == p)
    {
        printf("The LinkList is Empty!");
    }
    else
    {
        while(p->num !=loc && p->next !=NULL)
        {
            q=p;
            p = p->next;
        }
        if(p->next == NULL)
        {
            if(p ==*head)
            {
                if(p->num !=loc)
                {
                    printf("No such node in there!\n");
                }
                else
                {
                    *head = (*head)->next;
                    free(p);
                }
            }
            else//p !=*head
            {
                q->next = p->next;
                free(p);
            }
        }
        else
        {
            if(p == *head)
            {
                *head = p->next;
                free(p);
            }
            else
            {
                q->next = p->next;
                free(p);
            }
        }
    }
}


Link sort_list(Link head)//冒泡链表排序
{
	int i, j, temp, issorted;
	int count = 0;
	Link p = head, q = head;
	
	while(p->next != NULL)//查看链表中还剩下多少结点
	{
		p = p ->next;
		count++;
	}
	
	for(i=0; i<=count-1; i++)
	{
		q = head;
		issorted = 1;//假设剩下的元素已经排序好了
		for(j=0; j<=count-1-i; j++)
		{
			if((q->num) > (q->next->num))
			{
				temp = q->next->num;
				q->next->num = q->num;
				q->num = temp;
				issorted = 0;//一旦需要排序,就说明剩下的元素没有排序好
			}
			q = q->next;
		}
		if(issorted) break;
	}
	
	return head;//最终将头结点返回出去
}

void clear_list(Link head)
{
	Link p;
	while(head->next !=NULL)
    {
		p = head;
		head = head->next;
        free(p);
    }
}

void release_list(Link *head)
{
	clear_list(*head);

	free(*head);

	(*head) = NULL;
}

Link reverse_list(Link head)//递归逆序
{
    Link new_head;                      

	if(head == NULL)
		printf("empty!\n\n");
	else
	{
	    if(head->next == NULL)
	    {
	        return head;                
	    }
	    else
	    {
	        new_head = reverse_list(head->next);
	        head->next->next = head;//作为局部头指针
	        head->next = NULL;//局部尾
	        return new_head;//作为整体返回给前级调用  
	    }
	}
}


Link reverse_list2(Link head)//非递归逆序
{
	Link p, q, r;
	p = head;
	q = p->next;
	p->next = NULL;

	while(q)
	{
		r = q->next;//两个杯子需要交换一定需要借助中间变量
		q->next = p;
		p = q;
		q = r;
	}

	return p;
}

运行结果:

排序结果图:如右图所示

插入结点,和指定删除结点如下所示:

逆序链表双重逆序分别调用不同算法结果如下所示:

 逆序的详解:


①:非递归法

不代表头结点的单链表逆序非递归算法:

首先,我们将链表想象成数组,那么数组是怎么逆序的呢?有多种方法;可以是前尾相移法,还有就是两两相移法。就跟冒泡排序一样全部都遍历一次,只不过冒泡是排序,逆置是遍历。

便历时有二分逆置遍历等等。我们先来复习一下,如何逆置数组。

如下所示:

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

#define N 11 //奇数需要多交换一次

void Half_reverse(int *num);
void FB_reverse(int *num);

int main()
{
    int num[N];
    int i, j;
	
    srand((unsigned)time(NULL));
	
    for(i = 0; i < N; i++)
    {
	num[i] = rand()%20+1;
	printf("%-3d", num[i]);
    }
    putchar('\n');

    Half_reverse(num);
	
    for(i=0;i<N;i++)
        printf("%-3d", num[i]);
    putchar('\n');
	
    FB_reverse(num);
    for(i=0;i<N;i++)
	printf("%-3d", num[i]);
    putchar('\n');


    return 0;
}

void Half_reverse(int *num)//采用二分法
{
    int temp, i;
    for(i =0 ; i < N/2+1; i++)
    {
	temp = num[i];
	num[i] = num[N-1-i];
	num[N-1-i] = temp;
    }
}

void FB_reverse(int *num)//前尾相移法,最适用
{
    int tmp, i, j;
    for(i = 0, j = N-1; i < j; i++, j--)
    {
	tmp = num[i];
	num[i] = num[j];
	num[j] = tmp;
    }
}

那么我们把数组逆置的知识用在链表上呢?假想链表就是一个特殊的数组。我们应该如何逆置?

具体的实现思路如下


②:递归法实现

实现思路如下:

这是一个不带表头的单链表,所以递归的截止点就在于头指针什么时候指向NULL;然后逐层回归;这里我们仍然必须采用中间递归的方式来完成逆置。

4.求素数

求素数,我做了如下改正:

除了打印1-100以内的素数,不是素数就用0来填充。我使用了接收控制台输入的写法:并调用了自己写的atoi()函数,解决的bug如下:
当用户输入2dwa3jhkdjh4a3w7时,do_atoi()会将2337作为最后的整型变量值返回;以至于任何含有非数字字符的字符我都会跳过并逐个判断命令行输入的有效数字字符串转换后它是不是素数,是就输出,不是该位就用零来填充。

代码如下所示:

#include <iostream>
using namespace std;
#include <math.h>

int do_atoi(char *str);
int do_sushu(int val);

int main(int ac, char **av)
{
    int val;
    while(--ac)
    {
	val = do_atoi(*++av);
	cout<<do_sushu(val)<<" ";
    }
    cout<<endl;
//0-100以内
    for(int i = 2; i<=100; i++)
    {
	cout<<do_sushu(i)<<" ";
	if(i%10 == 0)
	    cout<<endl;
    }
	
    cout<<endl;
    return 0;
}

int do_atoi(char *str)
{
    int result = 0;
    int sign = 1;

    while(*str)
    {
	if(*str == '-')
	    sign = -1;
	else if((*str >= '0') && (*str <= '9'))
	    result = result*10+((*str) - 48);
	else
	    sign = 1;

	str++;
    }
    return (sign * result);
}

int do_sushu(int val)
{
    int result, i;
    int k = (int)sqrt((double)val);

    for(i=2; i <= k; i++)
    {
	if(val%i == 0)
	    break;
    }

    if(i > k)//全遍历结束
	return val;
    else
        return 0;
}

代码执行效果:

5.逆序一个句子(不改变一个单词的拼写)

解决思路:

①:首先要先逆序一个单词

②:将整个句子作为一个整体,调用逆序单词函数。将整体再次逆序即可。

实现方向:

采用从终端输入的方式(不是从命令行输入):

代码如下:

#include <iostream>
#define BUF_SIZE 245
using namespace std;

char *reverse(char *str);
void reverse_word(char *p, char *q);

int main(void)
{
    char str[BUF_SIZE];
    cout<<"input your strings>>>>:"<<endl;

    while(scanf("%[^\n]", str) !=0)//when you input enter,exit
    {
	getchar();//catch NUL
	cout<<"now you process "<<str<<endl;
	cout<<"The finally strings is "<<reverse(str)<<endl;
	cout<<"input your strings>>>>:"<<endl;
    }
	
    return 0;
}

void reverse_word(char *p, char *q)
{
  char tmp;
  while(p < q)//首与末
  {
  	tmp = *p; *p = *q; *q = tmp;
  	p++;q--;
  }
}

char *reverse(char *str)
{
    char *p, *q;
    p = q = str;

    while(*q)
    {
	if(*q ==' ')//我们以空格作为一个单词的分界点
	{
	    reverse_word(p, q-1);
	    q++;
	    p = q;
	}
	q++;
    }
    reverse_word(p, q-1);//最后一个单词
    reverse_word(str, q-1);//逆序一个整体

    return str;
}

实现结果和潜在BUG:

当用户输入i ___love____china时(“_”表示空格),___love也是一个单词吗?显然不是,我在逆序的时候便出现了这种问题。

解决方法:

·提前判别.而不是采用到点判决。

#include <iostream>
#define BUF_SIZE 245
using namespace std;

char *reverse(char *str);
void reverse_word(char *p, char *q);

int main(void)
{
    char str[BUF_SIZE];
    cout<<"input your strings>>>>:"<<endl;

    while(scanf("%[^\n]", str) !=0)//when you input enter,exit
    {
	getchar();//catch NUL
	cout<<"now you process "<<str<<endl;
	cout<<"The finally strings is "<<reverse(str)<<endl;
	cout<<"input your strings>>>>:"<<endl;
    }

    return 0;
}

void reverse_word(char *p, char *q)
{
  char tmp;
  while(p < q)//首与末
  {
  	tmp = *p; *p = *q; *q = tmp;
  	p++;q--;
  }
}

char *reverse(char *str)
{
    char *p, *q;
    p = q = str;

    while(*q)
    {
	if(((*q) != ' ') && (*(q+1) == ' '))
	{
	    reverse_word(p, q);

	    while(*(++q) == ' ');
	    p = q;
	}
	q++;
    }
    reverse_word(p, q-1);
    reverse_word(str, q-1);//逆序一个整体

    return str;
}

 检测:

当然原先的空格我没有作删除操作,该有的还是有的,只是我统计了正确的单词数目。

。。。题目都写完了,心里松了一口气。明天继续努力!!

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值