有序顺序表合并
将两个有序顺序表合并为一个新的有序顺序表。并由函数返回结果顺序表
算法思想
按顺序不断取下两个顺序表表头较小的结点存入新的顺序表中,然后,看哪个表还有剩余,将剩下的部分加到新的顺序表后面
bool Merge(SqList A, SqList B, SqList &C)
{
//将顺序表A与B合并为一个新的有序顺序表C
if (A.length+B.length > C.maxSize) //大于顺序表的最大长度
return false;
int i = 0, j = 0, k = 0;
while (i < A.length && j < B.length) //循环,两两比较,小的存入结果表
{
if (A.data[i] <= B.data[j])
C.data[k++] = A.data[i++];
else
C.data[k++] = B.data[j++];
}
while (i < A.length) //还剩一个没有比较完的顺序表
C.data[k++] = A.data[i++];
while (j < B.length)
C.data[k++] = B.data[j++];
C.length = k;
return true;
}
- 将A,B两个顺序表合并到C中,所以C的大小要比A和B加起来大
- 分别用i和j遍历A和B,比较,取小的赋给在C的指针k
- 当循环只可能有两种情况,要么A遍历完,或B遍历完毕
- 将没有遍历完的一个顺序表接着遍历,将剩余的数据直接尾插到C中
- 最后更改C的大小为k
查找x
线性表中的元素递增有序且按顺序存储于计算机内,要求设计一个算法,完成用最少时间在表中查找数值为x的元素,若找到,将其与后继元素位置相交换,若找不到,将其插入表中并使表中元素仍递增有序
算法思想
顺序存储的线性表递增有序,可以折半查找
void SearchX(ElemType A[], ElemType x)
{
int left = 0, right = n-1, mid;
while (left <= right)
{
mid = (left + right) / 2; //找中间位置
if (A[mid] == x)
break; //找到x,退出while循环
else if (A[mid] < x)
left = mid + 1; //到中点mid的右半部去查
else
right = mid - 1; //到中点mid的左半部去查
}
if (A[mid] == x && mid != n - 1) //若最后一个元素与x相等,则不存在与后继节点交换的操作
{
int t = A[mid];
A[mid] = A[mid + 1];
A[mid + 1] = t;
}
if (left > right) //查找失败,插入x
{
for (i = n - 1; i > right; i--)
{
A[i+1] = A[i]; //后移元素
}
A[i+1] = x; //插入x
}
}
- 用left和right指针,分别指向数组的左端和右端,并计算出中间的元素,mid的位置
- 每次循环mid和x比较大小,判断x在mid的左边还是右边,然后去掉没有x的一半
- 直到mid等于x退出循环,否则直到left和right相遇
- 循环结束后,当mid等于x,找到x,并且x不是最后一个元素,就交换mid和mid+1位置的元素
- 判断left是否大于right,如果大于,表明没有找到x,需要将x插入到合适的位置
- i从后往前遍历,直到将元素挨个往后移,直到i=right结束,在i+1,也就是right+1的位置插入x
查找元素是x,此时left=0,right=3,计算出mid等于1
因为3 < x,更新left=mid+1=2
现在left=2,right=3,mid=2,5 > x,更新right=mid-1=1
现在right=1,left=2,right>left
这时应该在right+1的位置插入x
因为right=mid-1,而mid位置大于x
在mid大于 x 的情况下,right 指向的元素是当前 mid 左边的最大值。
如果left>right,
right 会是最后一个被检查的元素,也是最后一个比 x 小的元素的位置。
right每次更新都会指向一个比x小的数,并且right是mid-1;而mid的值大于x,所以right是最后一个比 x 小的元素
在二分查找过程中,right 指针的作用是标识当前查找区间的右边界。当循环结束时
-
如果 left > right,说明没有找到 x,而 right 仍然指向最后一个被检查的元素。
-
在这种情况下,right 的值表示了当前查找范围的右边界。
-
right 的值实际上是比 x 小的最大元素的索引。
-
right + 1 就是 x 应该插入的位置,因为在有序数组中,x 的值应该在 right 之后。
x 插入到 right + 1位置
- 所有在 x 之前的元素仍然保持在左侧(
A[0]
到A[right]
) - 新插入的元素 x 位于正确的位置,确保数组依然有序。
查找交集
给定三个序列A,B,C,长度均为n,且均为无重复元素的递增序列,逐行输出同时存在于这三个序列当中的所有元素
算法思想
使用三个下标变量从小到大遍历数组。当三个下标变量指向的元素相等时,输出并向前推进指针,否则仅移动小于最大元素的下标变量,直到某个下标变量移出数组范围,即可停止。
void samekey(int A[], int B[], int C[], int n)
{
int i=0,j=0,k=0; //定义三个工作指针
while(i < n && j < n && k < n) //检查是否都在数组范围内
{
i£(A[i] == B[j] && B[j] == C[k]) //如果三个数组当前元素相同
{
print£("%d\n", A[i]); //输出相同的元素
i++;
j++;
k++;
}
else
{
//找到当前三者中最大的元素
int maxNum = A[i];
if (B[j] > maxNum)
maxNum = B[j];
if (C[k] > maxNum)
maxNum = C[k];
//移动指针以找到下一个可能的相同元素
if(A[i] < maxNum)
i++;
if(B[j] < maxNum)
j++;
if(C[k] < maxNum)
k++;
}
}
}
1和0小于2,往后++
全是2,输出2,然后全部++
查找中位数
一个长度为L的升序序列S,处在第L/2个位置的元素称为S的中位数
两个序列的中位数是含它们所有元素的升序序列的中位数
有两个等长升序序列A,B,找出两个序列A和B的中位数
算法思想
分别求两个升序序列A,B的中位数,设为a和b
- 若a = b,a和b即为所求中位数,算法结束
- 若a < b,则舍弃序列A中较小的一半,同时舍弃序列B中较大的一半,要求两次舍弃的长度相等
- 若a > b,则舍弃序列A中较大的一半,同时舍弃序列B中较小的一半,要求两次舍弃的长度相等
在保留的两个序列中,重复这个过程,直到两个序列中均只剩一个元素为止,其中较小的一个就是中位数
int M_Search(int A[], int B[], int n)
{
int l1, l2, m1, m2, r1, r2;
l1 = l2 = 0;
r1 = r2 = n - 1;
while (l1 != r1 || l2 != r2)
{
m1 = (l1 + r1) / 2;
m2 = (l2 + r2) / 2;
if (A[m1] == B[m2])
return A[m1]; //满足条件1,返回中位数
if (A[m1] < B[m2]) //满足条件2
{
if ((l1 + r1) % 2 == 0) //若元素个数为奇数
{
l1 = m1; //舍弃A中间点以前的部分,保留中间点
r2 = m2; //舍弃B中间点以后的部分,保留中间点
}
else //若元素个数是偶数
{
l1 = m1 + 1; //舍弃A的前半部分
r2 = m2; //舍弃B的后半部分
}
}
else //满足条件3
{
if ((l1 + r1) % 2 == 0) //若元素个数是奇数
{
r1 = m1; //舍弃A中间点以后的部分,且保留中间点
l2 = m2; //舍弃B中间点以前的部分,且保留中间点
}
else //若元素个数是偶数
{
r1 = m1; //舍弃A的后半部分
l2 = m2 + 1; //舍弃B的前半部分
}
}
}
return A[l1] < B[l1] ? A[l1] : B[l2];
}
算法的时间复杂度为 O ( log 2 N ) O(\log_{2}N) O(log2N),空间复杂度为 O ( 1 ) O(1) O(1)。
查找主元素
已知一个整数序列A,长度为n,若存在m个数的值相等,且m>n/2,则称这个数是A的主元素,否则A没有主元素
找出A的主元素,若存在,输出该元素,否则输出-1
算法思想
算法的基本设计思想:算法的策略是从前向后扫描数组元素,标记出一个可能成为主元素的元素 Num。然后重新计数,确认Num是否是主元素。
选取候选的主元素,依次扫播所给数组中的每个整数,将第一个遇到的整数Nam保存到c中、记录Num的出现次数为1;若遇到的下一个整数仍等于Num,则计数加1,否则计数减1;当计数减到0时,将遇到的下一个整数保存到c中,计数重新记为1,开始新一轮计数、即从当前位曾开始重复上述过程,直到扫描完全部数组元素。
判断c中元素是否是真正的主元素。再次扫描该数组,统计c中元素出现的次数,若大于n/2,则为主元素,否则,序列中不存在主元素。
int Majority(int A[], int n)
{
int i, c, count = 1; //初始化候选元素c和计数count
c = A[0]; //将第一个元素设置为候选元素
for (i = 1; i < n; i++) //遍历数组
{
if (A[i] == c) //如果当前元素等于候选元素,增加计数
count++;
else //否则,减少计数
{
if (count > 0) //如果计数大于0,只减少计数
count--;
else //如果计数变为0,换成新的候选元素
{
c = A[i]; //将当前元素设置为新的候选元素
count = 1; //重置计数
}
}
}
//检查候选元素是否真的是主元素
if (count > 0) //如果存在候选元素
{
//重新遍历数组,计算候选元素的出现次数
for (i = count = 0; i < n; i++)
{
if (A[i] == c)
count++;
}
}
//如果候选元素的出现次数超过 n/2,返回它作为主元素
if (count > n/2)
return c;
else //否则返回 -1,表示没有主元素
return -1;
}
实现的程序的时间复杂度为O(n),空间复杂度为O(1)。
查找未出现的最小正整数
给定一个含n个整数的数组,找出数组中未出现的最小正整数
算法思想
要求在时间上尽可能高效,因此采用空间换时间的办法。分配一个用于标记的数组B[n]
,用来记录A中是否出现了1n中的正整数,`B[0]`对应正整数1,`B[n-1]`对应正整数n,初始化B中全部为0。由于A中含有n个整数,因此可能返回的值是1n+1,当中n个数恰好为1n时返同n+1。当数组A中出现了小于或等于0或大于n的值时,会导致1n中出现空余位置;返同结果必然在1~n中,因此对于A中出现了小于或等于0或大于n的值,可以不采取任何操作。
经过以上分析可以得出算法流程:从A[0]
开始遍历,若0<A[1]<=n
,则令B[A[1]-1]=1
否则不做操作。对A遍历结束后,开始遍历数组B,若能查找到第一个满足B[i]==0
的下标i,返回i+1即为结果,此时说明A中未出现的最小正整数在1和n之间。若B[1]
全部不为0,返回i+1 (跳出循环时i=n,i+1等于n+1),此时说明A中未出现的最小正整数是 n+1。
int findMissMin(int A[], int n)
{
int i, *B; //标记数组
B = (int*)malloc(sizeof(int) * n); //分配空间
memset(B, 0, sizeof(int) * n); //赋初值为0
for(i = 0; i < n; i++)
if(A[i] > 0 && A[i] <= n) //若A[i]的值介于1~n,则标记数组B
B[A[i] - 1] = 1;
for(i = 0; i < n; i++)
if (B[i] == 0) //扫描数组 B,找到目标值
break;
return i+1;
}
- 标记数组B
- 是一个辅助数组,大小为n,用于记录哪些数字在数组A中出现过。B的初值全设为0,表示尚未标记
- 如果A中出现了某个数字x(1<=x<=n),就把
B[x-1]
设为1,表示数字x存在于数组A中
- 标记过程
遍历数组A,对于每个A[i]
,如果它是介于 1 到 n 之间的正整数,则在 B 中标记对应的位置B[A[i] - 1]
为 1。比如,A[i] = 3
,就标记B[2] = 1
,表示 3 存在于 A 中。 - 查找最小缺失正整数
- 遍历标记数组 B,找到第一个
B[i]
为 0 的位置,说明数字i+1
是缺失的最小正整数。 - 返回
i+1
作为结果,因为i
对应的数字是i+1
时间复杂度:遍历A一次,遍历B一次,两次循环内操作步骤为0(1)量级,因此时间复杂度为 O(n)。空间复杂度:额外分配了B[n]
,空间复杂度为0(n)。