C语言课程实验10

第十次实验

题目一

(1)完全用指针操作,像一个数组输入10个值,并输出其中的最大值及其下标。

思路

就是把s数组的索引改成指针。没啥好说的,代码见。

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

#define N 10

int main()
{
	int arr[N] = { 0 };
	int* pMax = arr;

	printf("请输入十个整数:\n");
	for (int i = 0; i < N; ++i)
	{
		scanf("%d", arr+i);
		if (i > 0 && *pMax < *(arr + i))
		{
			pMax = arr + i;
		}
	}

	printf("最大值为:%d, 下标为%d\n", *pMax, pMax-arr);
	return EXIT_SUCCESS;
}
题目二

(2)在冒泡排序中,需要比较相邻的两个元素的大小,并决定是否需要交换。改写冒泡排序的程序,其中至少包含两个函数:bubble_sort(int a[],int n)和swap,bubble_sort用于将数组a中的n个元素排序,其中调用函数swap来交换两个数。

思路

回头再扯扯冒泡的优化问题。

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

void swap(int* a, int* b)
{
	int tmp = *a;
	*a = *b;
	*b = tmp;
}

void bubble_sort(int a[], int n)
{
	int boundary = n;
	for (int i = 0; i < n; ++i)
	{
		int pos = 0;
		int isChanged = 0;
		for (int j = 0; j < boundary-1; ++j)
		{
			if (a[j] > a[j+1])
			{
				swap(a + j, a + j + 1);
				isChanged = 1;
				pos = j+1;
			}
		}
		boundary = pos;
		if (!isChanged) break;
	}

}

int main()
{
	int arr[8] = { 5,8,6,3,9,2,1,7 };
	bubble_sort(arr, 8);

	for (int i = 0; i < 8; ++i)
	{
		printf("%d ", arr[i]);
	}
	return EXIT_SUCCESS;
}
题目三

(3)编写一个函数void deleteChar(char *s,char c),在字符串s中删除字符c。如果该字符不存在,则什么都不做。例如,在“Duang Huan”中删除了‘u’后,变成了字符串“Dang Han”。

思路1

最简单的想法就是遍历字符串s中的每个元素,出现c就将c后面的字符全部向前移动一位,这样c就被覆盖了;依次不断进行下去,直到遍历结束。

例如

思路一简单图解

思路二(采用)

在思路一中,通过第一轮比较后移动元素时发现,我们将后面的u也一起移动了,这样,在后续p指针的移动比较时,需要将曾经移动的元素也一一比较。如果在移动的过程中也进行比较,我们可以快速的定位下一个u的位置,提高了程序效率。

思路二简单图解

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


void deleteChar(char* s, char c)
{
	char* p = s;
	int step = 1;// 移动步长
	// 找第一个c的位置
	while (*p != c && *p != '\0') ++p;
	// s中存在c,执行删除操作
	while (*p != '\0')
	{
		char* tmp = p+1;
		if (*tmp == c) ++step;
		else *(p-step+1) = *tmp;
		++p;
	}
}

int main()
{
	//char s[] = "uduuang uhuan";
    char s[] = "Duang Huan";
	deleteChar(s, 'u');
	printf("%s",s);

	return EXIT_SUCCESS;
}
题目四

(4)编写一个函数str_cat,实现函数strcat功能。要求用指针实现。

思路

strcat()是C语言头文件string.h中的字符串连接函数。

仿照设计str_cat(const char* dst, char* src),直接将字符串src复制到dst的末尾即可。

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

// 假设dst空间足够大
void str_cat(char* dst, const char* src)
{
	char* d = dst;
	const char* s = src;
	while (*d != '\0') ++d;
	while (*s != '\0')
	{
		*d = *s;
		++d; ++s;
	}
	*d = *s;
}

int main()
{

	char dst[100] = "abcdefg";
	char src[10] = "abcde";
	str_cat(dst, src);
	printf("%s", dst);
	return EXIT_SUCCESS;
}
题目五

(5)输入一个长度大于3的字符串,从中随机选取3个字符构成一个新的字符串,输出这个新的字符串。分两种情况:
a)一个位置上的字符可以多次被选取。
b)一个位置上的字符只能被选取一次。

思路

第一种情况比较容易,用随机数生成三个随机位置,按位置取字符串中的字符,然后拼接在一起即可。

至于第二种情况,只需要保证生成的三个随机位置不重复即可。可以用数组存储取过的位置,这样每次生成随机数都检验是否曾经生成过一样的随机数即可。如果数组中存在重复的数,就再次生成;如果不存在,说明不重复,可以使用,并将该数放入数组。

#define _CRT_SECURE_NO_WARNINGS

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

#define MAX_SIZE 100 

int str_len(const char* s)
{
	int len = 0;
	while (*(s + len) != '\0') ++len;
	return len;
}

int found( int *arr, int pos)
{
	int flag = (*arr)&(1 << pos);
	*arr = (*arr) | (1 << pos);
	return flag;
}

void case1(const char* s, char* d, int n)
{
	int len = str_len(s);
	for (int i = 0; i < n; ++i)
	{
		int tmp = rand() % len;
		*(d + i) = *(s + tmp);
	}
}

void case2(const char* s, char* d, int n)
{
	int len = str_len(s);
	int arr_flag = 0;
	for (int i = 0; i < n; ++i)
	{
		int tmp_pos = 0;
		while (1) 
		{
			tmp_pos = rand() % len;
			if (!found(&arr_flag, tmp_pos)) break;
		}
		*(d + i) = *(s + tmp_pos);
	}
}


int main()
{
	srand((unsigned int)time(NULL));

	printf("Please input string end with '#':\n");
	char s[MAX_SIZE];
	char ch;
	int i = 0;
	while ((ch = getchar()) != '#') s[i++] = ch;
	s[i] = '\0';
	getchar();
	
	char d[5] = {0};
	case2(s,d,3);
	printf("%s",d);
	return EXIT_SUCCESS;
}

题目六

(6)输入一个n位数(n>=3),找出其中3个连续的数字构成的数中最大的那一个。例如,输入26895,则3个连续的数字构成的数是268、689、895.

思路

在位数比较少的情况下,可以直接计算出所有所有的连续三位数,并比较大小,输出最大数即可。

但是一旦n过大,或者找出最大的连续的100位数这种方法就无法处理了。

因此,我们可以这样考虑:

因为在n位数中取连续的三位数比较容易,困难的是比较大小,因此,本题的关键在于如果在取数的过程中找出最大值,问题又可以减小为如何比较三位数大小。

比较三位数大小就比较简单了,百位与百位比,十位与十位比,个位与个位比即可。

因为是连续的数字,因此本题中只需比较n中0~n-3号位置的元素,即2、6、8的大小,也就是说先比较三位数中的百位数,取百位最大的,那么这个三位数也是最大的。本题中不难看出8是最大的。

但如果出现百位数相同呢,又或者百位十位都相同呢?

如果出现这种情况,就依次比较下去,直至分出大小。

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

void exam(const char* s, int n)
{
	const char* p = s;
	const char* pMax = s;
	++p;
	while (*(p+n-1) != '\0')
	{
		if (*pMax < *p) pMax = p;
		else if (*pMax == *p)
		{
			int flag = 0;
			for (int i = 0; i < n; ++i)
			{
				if (*(pMax + i) < *(p + i)) { flag = 1; break; }
			}
			if (flag) pMax = p;
		}
		++p;
	}
	for (int i = 0; i < n; ++i)
	{
		printf("%c", *(pMax+i));
	}
}


int main()
{
	char s[] = "2688879795";
	exam(s, 3);

	return EXIT_SUCCESS;
}
题目七

(7)输入一个字符串,其中含有带括号的子串,输出不含带括号子串的字符串。例如,输入“Microsoft(MS) Office”,输出“Microsoft Office”。

思路

在字符串中寻找 ‘(’ 和 ‘)’ ,同时存在且出现循序合法就将括号连同中间的字符串一起删除

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

// 功能:去除字符串中所有格式正确的括号(不支持括号的嵌套),若要支持括号的嵌套,需要使用栈进行操作
void removeBrackets(char* s)
{
	char* p = s, * left = NULL, * right = NULL;
	while (*p != '\0')
	{
		if (*p == '(') left = p;
		else if (*p == ')') right = p;

		if (left && right && right > left)
		{	// 移动操作
			// 递归移动
			removeBrackets(p + 1);
			int step = right - left + 1;	// 移动步长
			// 全部移动(由于递归的原因,后面的括号都已经去除)
			while (*right != '\0')
			{
				*left = *(right + 1);
				++left; ++right;
			}
			// 移动完成,重置left,right
			left = right = NULL;
		}

		++p;
	}
}
int main()
{
	char s[] = "Microsoft(MS) Office";
	removeBrackets(s);

	printf("%s", s);

	return EXIT_SUCCESS;
}
题目八

(8)读入一个含有注释信息的C程序,将其中的所有注释信息清理掉后,保存为另一个文件。

思路

没想到特别好的方法,因此,直接将文件所有内容读入内存处理。程序效率低,最好使用两个文件指针,一个读,一个写。

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

#define FILE_MAX_SIZE (1<<20)

// 返回'/'的指针
char* hasTheOtherSymbol(char* s)
{
	char* p = s + 2;
	while (*p != '\0')
	{
		if (*p == '*' && *(p + 1) == '/') break;
		++p;
	}
	return p + 1;
}
int removeSingleLineComment(char* s)
{
	char* p = s+2;
	char* src = s;
	while (*p != '\0' && *p != '\n') ++p;
	while (*p != '\0')
	{
		*src = *(p + 1);
		++src; ++p;
	}
	return p - src + 1;
}

// src指向第一个/,dst指向后一个/
int removeMultiLineComment(char* src, char* dst)
{
	char* from = src;
	char* p = dst + 1;
	while (*p != '\0')
	{
		*src = *p;
		++src; ++p;
	}
	return p - src + 1;
}

int removeComment(char* s, int len)
{
	char* p = s;
	while (*p != '\0')
	{
		if (*p == '/' && *(p + 1) == '/')
		{
			// 将//到\n之间的字符全部删除
			len -= removeSingleLineComment(p);
		}
		else if (*p == '/' && *(p + 1) == '*')
		{
			char* dst = hasTheOtherSymbol(p);
			if (*dst != '\0')
			{
				len -= removeMultiLineComment(p, dst);
			}
		}
		++p;
	}
	return len;
}

int main()
{
	char* fileCharArray = (char*)malloc(sizeof(char) * FILE_MAX_SIZE);
	if (!fileCharArray)
	{
		printf("内存分配失败");
		exit(-1);
	}
	memset(fileCharArray, '\0', sizeof(char) * FILE_MAX_SIZE);
	int charArrayLength = 0,
		finalLength = 0;
	FILE* fp = NULL;
	if (!(fp = fopen("test.c", "r")))
	{
		printf("读取文件失败");
		exit(-1);
	}
	

	fseek(fp, 0L, SEEK_END);
	size_t fileLength = ftell(fp);
	fseek(fp, 0L, SEEK_SET);
	charArrayLength = fread(fileCharArray, sizeof(char), fileLength, fp);

	fp = freopen("test.c", "w", fp);
	finalLength = removeComment(fileCharArray, charArrayLength);

	fseek(fp, 0, SEEK_SET);
	memset(fileCharArray + finalLength+1, '\x20', sizeof(char) * (charArrayLength - finalLength));

	fwrite(fileCharArray, sizeof(char), charArrayLength, fp);

	fclose(fp);
	return EXIT_SUCCESS;
}
题目九

(9)“洗牌”是一种非常常见的操作。所谓洗牌,就是给定一个序列,随机产生一个新的排列。例如,数据序列是:5 18 36 21 11 25,每洗一次牌,就产生一个完全不同的新排列。写一个程序,实现洗牌操作。

思路

Knuth Shuffle算法

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

void swap(int* a, int* b)
{
	int tmp = *a;
	*a = *b;
	*b = tmp;
}

void Knuth_Shuffle(int arr[], int n)
{
	for (int i = n - 1; i >= 0; --i)
	{
		swap(&arr[rand() % (i+1)], &arr[i]);
	}
}

int main()
{
	srand((unsigned int)time(NULL));

	int arr[] = { 5, 18, 36, 21, 11, 25 };
	int n = 6;
	Knuth_Shuffle(arr, n);

	return EXIT_SUCCESS;
}

结语

  1. 代码仅对思路作出实现,效率比较低下,代码质量较差,还请谅解。
  2. 若以上思路或程序有误,还请不吝赐教。
  3. 如果您有改进意见,欢迎指出。
  4. 程序的相关问题都欢迎交流
  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值