跳转至

BB2-判断链表B是否是链表A的连续子序列

代码实现

C
/**
 * 判断链表B是否是链表A的连续子序列
 * @param A 链表A(母链表)
 * @param B 链表B(子链表)
 * @return B是A的连续子序列返回true,否则返回false
 */
bool find(LinkList A, LinkList B) {
    // 边界条件检查
    if (A == NULL || B == NULL) {
        return false;
    }

    // 空链表处理:空链表被认为是任何链表的子序列
    if (B->next == NULL) {
        return true;
    }

    // 如果A为空但B不为空,则B不可能是A的子序列
    if (A->next == NULL) {
        return false;
    }

    // pos指向A中当前尝试匹配的起始位置
    LinkList pos = A->next;

    // 在A的每个可能起始位置尝试匹配B
    while (pos != NULL) {
        LinkList p1 = pos;      // A的当前比较位置
        LinkList p2 = B->next;  // B的当前比较位置

        // 在当前位置尝试匹配整个B链表
        while (p1 != NULL && p2 != NULL) {
            if (p1->data == p2->data) {
                // 数据匹配,继续比较下一个结点
                p1 = p1->next;
                p2 = p2->next;
            } else {
                // 数据不匹配,跳出内层循环
                break;
            }
        }

        // 检查是否完全匹配B
        if (p2 == NULL) {
            // B已完全匹配,说明B是A的连续子序列
            return true;
        }

        // 当前位置匹配失败,尝试A的下一个起始位置
        pos = pos->next;
    }

    // 遍历完A的所有可能位置都未找到匹配
    return false;
}

/**
 * 优化版本:添加长度预检查
 * @param A 链表A
 * @param B 链表B
 * @return B是A的连续子序列返回true,否则返回false
 */
bool find_optimized(LinkList A, LinkList B) {
    if (A == NULL || B == NULL) {
        return false;
    }

    if (B->next == NULL) {
        return true;  // 空链表是任何链表的子序列
    }

    if (A->next == NULL) {
        return false;  // A为空但B不为空
    }

    // 计算两个链表的长度
    int lenA = 0, lenB = 0;
    LinkList temp;

    // 计算A的长度
    for (temp = A->next; temp != NULL; temp = temp->next) {
        lenA++;
    }

    // 计算B的长度
    for (temp = B->next; temp != NULL; temp = temp->next) {
        lenB++;
    }

    // 如果B比A长,不可能是子序列
    if (lenB > lenA) {
        return false;
    }

    // 在A中尝试匹配B
    LinkList pos = A->next;
    int max_start_pos = lenA - lenB;  // 最大起始位置

    for (int start_pos = 0; start_pos <= max_start_pos && pos != NULL; start_pos++) {
        LinkList p1 = pos;
        LinkList p2 = B->next;

        // 尝试匹配
        while (p1 != NULL && p2 != NULL && p1->data == p2->data) {
            p1 = p1->next;
            p2 = p2->next;
        }

        // 检查是否完全匹配
        if (p2 == NULL) {
            return true;
        }

        pos = pos->next;
    }

    return false;
}

/**
 * 获取链表长度
 * @param L 链表头指针
 * @return 链表长度
 */
int get_length(LinkList L) {
    if (L == NULL) return 0;

    int length = 0;
    LinkList p = L->next;
    while (p != NULL) {
        length++;
        p = p->next;
    }
    return length;
}

/**
 * 打印链表内容
 * @param L 链表头指针
 */
void print_list(LinkList L) {
    if (L == NULL || L->next == NULL) {
        printf("空链表\n");
        return;
    }

    LinkList p = L->next;
    printf("链表内容:");
    while (p != NULL) {
        printf("%d ", p->data);
        p = p->next;
    }
    printf("\n");
}

算法执行流程图解

匹配成功示例:

Text Only
链表A:[ ]→[1]→[2]→[3]→[4]→[5]→NULL
链表B:[ ]→[2]→[3]→[4]→NULL

执行过程:
pos=[1]: p1=[1], p2=[2] → 1≠2,匹配失败
         pos=[2]

pos=[2]: p1=[2], p2=[2] → 匹配,p1=[3], p2=[3]
         p1=[3], p2=[3] → 匹配,p1=[4], p2=[4]
         p1=[4], p2=[4] → 匹配,p1=[5], p2=NULL
         p2==NULL → 完全匹配,返回true

匹配失败示例:

Text Only
链表A:[ ]→[1]→[2]→[3]→[4]→[5]→NULL
链表B:[ ]→[2]→[4]→[3]→NULL

执行过程:
pos=[1]: p1=[1], p2=[2] → 1≠2,匹配失败
         pos=[2]

pos=[2]: p1=[2], p2=[2] → 匹配,p1=[3], p2=[4]
         p1=[3], p2=[4] → 3≠4,匹配失败
         pos=[3]

pos=[3]: p1=[3], p2=[2] → 3≠2,匹配失败
         pos=[4]
...以此类推,最终返回false

算法状态变化图解

Text Only
1
2
3
4
5
6
7
8
9
链表A:[ ]→[a]→[b]→[c]→[d]→[e]→NULL
链表B:[ ]→[b]→[c]→[d]→NULL

匹配过程:
起始位置1:[a] vs [b] → 失败
起始位置2:[b] vs [b] → 继续
           [c] vs [c] → 继续  
           [d] vs [d] → 继续
           NULL vs NULL → 成功

复杂度分析

时间复杂度:O(m×n)

  • 最坏情况:O(m×n),其中m是链表A的长度,n是链表B的长度
  • 最好情况:O(m),第一个位置就匹配失败
  • 平均情况:O(m×n),但在实际应用中通常表现较好
  • 举例:A长度1000,B长度10,最坏情况下需要比较10000次

空间复杂度:O(1)

  • 辅助变量:只使用常数个指针变量(pos, p1, p2等)
  • 无额外存储:不需要额外的数据结构
  • 总体空间复杂度O(1)

算法优化版本

C
/**
 * KMP算法在链表中的应用(理论)
 */
typedef struct {
    LinkList pattern;
    int* next;  // 部分匹配表
} KMP_LinkList;

bool kmp_find(LinkList text, LinkList pattern) {
    // 实现较为复杂,需要将KMP算法适配到链表结构
    // 这里给出基本思路

    if (text == NULL || pattern == NULL) return false;
    if (pattern->next == NULL) return true;
    if (text->next == NULL) return false;

    // 1. 计算pattern的next数组
    // 2. 在text中进行KMP匹配
    // 3. 利用next数组避免重复比较

    return false;  // 简化版本
}

/**
 * 带位置信息的匹配函数
 * @param A 链表A
 * @param B 链表B
 * @param position 匹配位置(输出参数)
 * @return 是否匹配成功
 */
bool find_with_position(LinkList A, LinkList B, int* position) {
    if (A == NULL || B == NULL || position == NULL) {
        return false;
    }

    if (B->next == NULL) {
        *position = 0;
        return true;
    }

    if (A->next == NULL) {
        return false;
    }

    int pos_index = 0;
    LinkList pos = A->next;

    while (pos != NULL) {
        LinkList p1 = pos;
        LinkList p2 = B->next;

        while (p1 != NULL && p2 != NULL && p1->data == p2->data) {
            p1 = p1->next;
            p2 = p2->next;
        }

        if (p2 == NULL) {
            *position = pos_index;
            return true;
        }

        pos = pos->next;
        pos_index++;
    }

    return false;
}

错误处理完善版本

C
/**
 * 安全版本:添加完整的错误处理
 */
bool find_safe(LinkList A, LinkList B) {
    // 参数验证
    if (A == NULL) {
        printf("错误:链表A为空指针\n");
        return false;
    }

    if (B == NULL) {
        printf("错误:链表B为空指针\n");
        return false;
    }

    // 空链表处理
    if (B->next == NULL) {
        printf("链表B为空,认为是任何链表的子序列\n");
        return true;
    }

    if (A->next == NULL) {
        printf("链表A为空,非空链表B不可能是其子序列\n");
        return false;
    }

    // 匹配过程
    LinkList pos = A->next;
    int start_pos = 0;

    while (pos != NULL) {
        LinkList p1 = pos;
        LinkList p2 = B->next;
        int match_length = 0;

        // 尝试匹配
        while (p1 != NULL && p2 != NULL) {
            if (p1->data == p2->data) {
                p1 = p1->next;
                p2 = p2->next;
                match_length++;
            } else {
                break;
            }
        }

        // 检查是否完全匹配
        if (p2 == NULL) {
            printf("在链表A的第%d个位置找到匹配\n", start_pos);
            return true;
        } else if (match_length > 0) {
            printf("在位置%d部分匹配了%d个元素\n", start_pos, match_length);
        }

        pos = pos->next;
        start_pos++;
    }

    printf("未找到匹配的子序列\n");
    return false;
}

性能测试对比

C
/**
 * 性能测试函数
 */
void performance_test() {
    // 创建测试链表
    LinkList A = create_test_list(1000);  // 长度1000的链表
    LinkList B = create_test_sublist(10); // 长度10的子链表

    clock_t start = clock();
    for (int i = 0; i < 1000; i++) {
        find(A, B);
    }
    clock_t naive_time = clock() - start;

    start = clock();
    for (int i = 0; i < 1000; i++) {
        find_optimized(A, B);
    }
    clock_t optimized_time = clock() - start;

    printf("朴素算法耗时: %ld\n", naive_time);
    printf("优化算法耗时: %ld\n", optimized_time);

    // 释放内存
    destroy_list(A);
    destroy_list(B);
}

实际应用示例

C
/**
 * 实用函数:在链表序列中查找模式
 */
typedef struct {
    int id;
    char name[50];
} Student;

typedef struct StudentNode {
    Student data;
    struct StudentNode* next;
} StudentNode, *StudentList;

bool find_student_sequence(StudentList students, Student pattern[], int pattern_len) {
    if (students == NULL || pattern_len <= 0) return false;

    StudentNode* pos = students->next;

    while (pos != NULL) {
        StudentNode* p1 = pos;
        int i = 0;

        while (p1 != NULL && i < pattern_len) {
            if (p1->data.id == pattern[i].id) {
                p1 = p1->next;
                i++;
            } else {
                break;
            }
        }

        if (i == pattern_len) {
            return true;
        }

        pos = pos->next;
    }

    return false;
}

/**
 * 多个子序列搜索
 */
int find_multiple_subsequences(LinkList A, LinkList patterns[], int pattern_count) {
    for (int i = 0; i < pattern_count; i++) {
        if (find(A, patterns[i])) {
            printf("找到第%d个子序列\n", i + 1);
            return i;
        }
    }
    return -1;
}

算法特点

核心思想: 在链表A的每个可能起始位置尝试匹配整个链表B,一旦发现不匹配就移动到下一个位置重新开始。

关键技术: 1. 双重遍历:外层遍历A的起始位置,内层进行链表匹配 2. 提前终止:发现不匹配立即跳出内层循环 3. 指针操作:正确处理链表指针的移动和比较

优势: - 实现简单:逻辑清晰,易于理解和实现 - 正确性高:算法简单,不容易出错 - 适用广泛:适用于各种数据类型的链表

局限性: - 效率较低:最坏情况下时间复杂度为O(m×n) - 重复比较:可能对同一结点进行多次比较 - 不适用于大型链表:对于很长的链表效率较差

应用场景: - 链表数据的模式匹配 - 生物信息学中的序列匹配(DNA、蛋白质序列) - 日志分析中的模式查找 - 网络数据包序列分析 - 游戏开发中的行为序列检测

算法改进方向: 1. KMP算法适配:避免重复比较 2. 哈希优化:使用滚动哈希快速筛选 3. 并行处理:对大型链表进行并行匹配

这种链表子序列匹配算法是链表操作中的经典问题,在实际应用中具有重要的实用价值。