小白第一次发博客,初来乍到,请多关照//
最近在学C语言,发现数组排序去重这类问题有一些细节,产生的问题较多,正好最近想试一下写第一篇博客,于是就有了这个文章_
下面两题的基本思路一样,区别是重复的元素保留一个还是全部删去,以及最多重复的个数。
问题1:字符串中字符排序
【问题描述】
编写一个程序,从键盘接收一个字符串,然后按照字符顺序从小到大进行排序,并删除重复的字符。
【输入形式】
用户在第一行输入一个字符串。
【输出形式】
程序按照字符(ASCII)顺序从小到大排序字符串,并删除重复的字符进行输出。
【样例输入】
badacgegfacb
【样例输出】
abcdefg
问题2:数组异或集
【问题描述】
从标准输入中输入两组整数(每行不超过20个整数,每组整数中元素不重复),合并两组整数,去掉在两组整数中都出现的整数,并按从大到小顺序排序输出(即两组整数集"异或")。
【输入形式】
首先输入第一组整数,以一个空格分隔各个整数;然后在新的一行上输入第二组整数,以一个空格分隔,行末有回车换行。
【输出形式】
按从大到小顺序排序输出合并后的整数集(去掉在两组整数中都出现的整数,以一个空格分隔各个整数)。
【样例输入】
5 1 4 32 8 7 9 -6
5 2 87 10 1
【样例输出】
87 32 10 9 8 7 4 2 -6
思路:
两道题都需要排序去重,故先排序;
问题1重复的保留一个,所以可以逐个遍历排序后的数组,如果该元素和它的下一个元素相同,则覆盖掉该元素,保留下一个;然后从下一个开始继续遍历。
但是实现的时候,会出现这种情况:(粗体位置为判定点a[i]和a[i+1])
aaabcdeg ⇒ aabcdeg ,大循环i++之后变成 aabcdeg ,这里a不会再删去,最终两个a都会留下。
因此这里去重之后要 i-- 让判定点归位, 变回 aabcdeg ,重新判定。
问题2两组数组不会重复,因此合并后的数组不会出现3个或以上元素相同的情况。
因此如果保留一个,可以不必i–。
但是由于需要两个均删除,由演示:(这里用字符替代数字了)
aabbccdegt ⇒ bbccdegt(覆盖2个) ⇒ bbccdegt ,最终留下两个b。
所以同样需要i–。
另有一套思路:重复的不输出,从输出端进行去重。
对于问题1,如果a[i]和a[i+1]相同,则不输出。这样对于长串的相同字符,只有最后一个会输出。
对于问题2,如果a[i]和a[i+1]相同,则跳过这两个直接判定下下个。由于每次枚举i自动加一,因此需要补充一个i++。
代码:(数组覆盖思路)
(问题1:字符串中字符排序)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char c[19999];
void swap(char *a,char *b){ //交换函数,即 {char x = a;a=b;b=x;}
char x = *a;
*a = *b;
*b = x;
}
int main(){
fgets(c, 19997, stdin); //输入,因为vs里面gets()已经无了所以作为替代
int lc = strlen(c); //字符串长度(统计多少个字符)
for (int i = 0;i<lc;i++){ //冒泡排序
for (int j = 0;j<lc-i-1;j++){ //lc-i再减一防止i==0时,j+1==lc越界
if (c[j]>c[j+1]) swap(&c[j],&c[j+1]);
}
}
for (int i = 0;i<lc-1;i++){ //判断去重,lc-1同理防越界
if (c[i]==c[i+1]) { //相等
for (int j = i;j<lc;j++) c[j]=c[j+1];//覆盖掉c[j],保留了c[j+1]
//这里为什么可以j+1==lc?因为要把字符串末尾的\0也挪下来
i--; //因为存在三个连续相等的情况,这里需要再次判断第二个和第三个是否相等
lc--; //串长-1(用来约束循环,不然c[i]会走到字符串的\0处,c[i+1]越界)
}
}
printf("%s",c);
return 0;
}
(问题2:数组异或集)
#include <stdio.h>
int a[999];
int main(){
int temp;
int s = 0; //数的个数
do { //获取第一个数组,直到回车换行
scanf("%d",&temp);
a[s++] = temp;
} while (getchar() != '\n'); //遇到数间空格继续,遇回车结束
do { //获取第二个数组,直到回车换行
scanf("%d",&temp);
a[s++] = temp;
} while (getchar() != '\n'); //遇到数间空格继续,遇回车结束
for (int i = 0;i<s;i++){ //冒泡排序
for (int j = 0;j<s-i-1;j++){ //s-i再减一,防i==0时j+1==s越界
if (a[j]<a[j+1]) {
int t = a[j];
a[j]=a[j+1];
a[j+1]=t;
}
}
}
for (int i = 0;i<s;i++){ //关键点: 逐个查重
if (a[i]==a[i+1]) { //与下一个重复
for (int j = i;j<s-2;j++) a[j]=a[j+2]; //将两个重复覆盖
s-=2; //总数少两个
i--; //重点! 删重复之后,下一次判定a[i+1]与a[i+2],会使得新的a[i]与a[i+1]相等情况被忽略
}
}
for (int i = 0;i<s;i++){ //输出
printf("%d ",a[i]);
}
return 0;
}
代码:(输出去重思路)
(问题1:字符串中字符排序)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char c[19999];
void swap(char *a,char *b){ //交换函数,即 {char x = a;a=b;b=x;}
char x = *a;
*a = *b;
*b = x;
}
int main(){
fgets(c, 19997, stdin); //输入,因为vs里面gets()已经无了所以作为替代
int lc = strlen(c); //字符串长度(统计多少个字符)
for (int i = 0;i<lc;i++){ //冒泡排序
for (int j = 0;j<lc-i-1;j++){ //lc-i再减一防止i==0时,j+1==lc越界
if (c[j]>c[j+1]) swap(&c[j],&c[j+1]);
}
}
for (int i = 0;i<lc;i++){ //输出去重,lc不减一为了让最后一个元素通过和结尾\0判定输出出来
if (c[i]!=c[i+1]) { //c[i]和下一个不相等
printf("%c",c[i]); //才输出
} //否则不输出
}
return 0;
}
(问题2:数组异或集)
#include <stdio.h>
int a[999];
int main() {
int temp;
int s = 0; //数的个数
do { //获取第一个数组,直到回车换行
scanf("%d", &temp);
a[s++] = temp;
} while (getchar() != '\n'); //遇到数间空格继续,遇回车结束
do { //获取第二个数组,直到回车换行
scanf("%d", &temp);
a[s++] = temp;
} while (getchar() != '\n'); //遇到数间空格继续,遇回车结束
for (int i = 0; i < s; i++) { //冒泡排序
for (int j = 0; j < s - i - 1; j++) { // s-i再减一,防i==0时j+1==s越界
if (a[j] < a[j + 1]) {
int t = a[j];
a[j] = a[j + 1];
a[j + 1] = t;
}
}
}
for (int i = 0; i < s; i++) { //输出查重
if (a[i] == a[i + 1]) { //与下一个重复
i++; //直接跳过a[i+1],下一轮判a[i+2]
} else
printf("%d ", a[i]); //不重复 才输出
}
return 0;
}
最后附上变式问题:读入字符串,可以3个或以上字符重复,要求重复的字符均不输出,如何操作?
变式问题代码:
(覆盖数组思路)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char c[19999];
void swap(char *a,char *b){ //交换函数,即 {char x = a;a=b;b=x;}
char x = *a;
*a = *b;
*b = x;
}
int main(){
fgets(c, 19997, stdin); //输入,因为vs里面gets()已经无了所以作为替代
int lc = strlen(c); //字符串长度(统计多少个字符)
for (int i = 0;i<lc;i++){ //冒泡排序
for (int j = 0;j<lc-i-1;j++){ //lc-i再减一防止i==0时,j+1==lc越界
if (c[j]>c[j+1]) swap(&c[j],&c[j+1]);
}
}
int cnt = 1; //重复个数,默认为1,即不重复
for (int i = 0;i<lc;i++){ //覆盖去重plus,这里i+1可以取到lc为了把最后一个元素判定掉
if (c[i]==c[i+1]) { //相等
cnt++; //攒一个重复数量
} else if (cnt-1>0) { //不相等且需要覆盖
for (int j = i-(cnt-1);j<=lc-cnt;j++) c[j]=c[j+cnt];//覆盖cnt个下来
//j的起点是开始重复的位置
//这里需要j+cnt==lc,因为要把字符串末尾的\0也挪下来
i-= cnt; //回去重判被覆盖的部分
lc-= cnt;//串长暴减
cnt=1; //cnt重新计数
} //不相等又不需要覆盖,无事发生
}
printf("%s",c);
return 0;
}
(输出去重思路)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char c[19999];
void swap(char *a,char *b){ //交换函数,即 {char x = a;a=b;b=x;}
char x = *a;
*a = *b;
*b = x;
}
int main(){
fgets(c, 19997, stdin); //输入,因为vs里面gets()已经无了所以作为替代
int lc = strlen(c); //字符串长度(统计多少个字符)
for (int i = 0;i<lc;i++){ //冒泡排序
for (int j = 0;j<lc-i-1;j++){ //lc-i再减一防止i==0时,j+1==lc越界
if (c[j]>c[j+1]) swap(&c[j],&c[j+1]);
}
}
int cnt = 1; //重复个数,默认为1,即不重复
if (c[0]!=c[1]) printf("%c",c[0]); //特判c[0],因为i-1得-1会越界
for (int i = 1;i<lc;i++){ //输出去重,lc不减一为了让最后一个元素通过和结尾\0判定输出出来
if (c[i]!=c[i+1] && c[i]!=c[i-1]) { //c[i]和下一个不相等,和前一个也不相等
printf("%c",c[i]); //才输出
} //否则不输出
}
return 0;
}
总结
要求重复保留一个的情况下:
无论多少个重复,都可以一个一个判定,但要注意覆盖数组法判定后i要减一(i–),输出去重不必i–;
要求重复元素均不保留的情况下:
存在2个以上元素互相重复时,需要注意判定该元素和前一个的关系,防止重复的最后一个没有判定到;最多重复2个的时候不需要(因为上一个重复完就不会和下一个重复啦,直接跳过就好)。