线性表练习之Example046-使双链表中结点保持按访问频度非增(递减)的顺序排列,同时最近访问的结点排在频度相同的结点前面

Posted 二木成林

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了线性表练习之Example046-使双链表中结点保持按访问频度非增(递减)的顺序排列,同时最近访问的结点排在频度相同的结点前面相关的知识,希望对你有一定的参考价值。

Example046

原文链接:Example046

题目

设头指针为 L 的带有表头结点的非循环双向链表,其每个结点中除有 pred(前驱指针)、data(数据)和 next(后继指针)域外,还有一个访问频度域 freq。在链表被启用前,其值均初始化为零。每当在链表中进行一次 Locate(L,x) 运算时,令元素值为 x 的结点中 freq 域的值增 1,并使此链表中结点保持按访问频度非增(递减)的顺序排列,同时最近访问的结点排在频度相同的结点前面,以便使频繁访问的结点总是靠近表头。试编写符合上述要求的 Locate(L,x) 运算的算法,该运算为函数过程,返回找到结点的地址,类型为指针型。

分析

本题考查的知识点:

  • 双链表
  • 双链表查找指定值结点
  • 双链表删除节点
  • 双链表插入节点

算法思想:首先在双链表中查找数据值等于 x 的节点 node,查到后,将该节点从链表上摘下(注意,恢复 node->priornode->next 的连接),然后顺着节点的前驱链查找该节点的插入位置(按频度递减,且排在同频度的第一个,即向前找到第一个比它的频度大的节点,插入位置为该节点之后),最后插入到该位置。

图解

C实现

核心代码:

/**
 * 按访问频度来修改指定值节点在链表中的位置
 * @param list 双链表
 * @param x 指定值
 */
DLNode *locate(DLNode **list, int x) 
    // 1.找到双链表中值为 x 的节点
    // 变量,记录双链表节点用于扫描链表,初始为双链表的第一个节点
    DLNode *node = (*list)->next;
    // 从头到尾扫描双链表所有节点
    while (node != NULL) 
        // 找到节点值等于 x 的节点
        if (node->data == x) 
            break;
        
        node = node->next;
    

    // 2.摘除 node 节点从双链表中
    // 变量,记录 node 节点的前驱节点
    DLNode *priorNode = node->prior;
    // 摘除 node 节点,将 pre 节点与 node 节点的后继节点连接起来
    if (node->next != NULL) 
        node->next->prior = node->prior;
    
    node->prior->next = node->next;

    // 3.修改访问频度并且找到 node 节点在链表中的插入位置
    // 将访问频度增加 1
    node->freq = node->freq + 1;
    // 找到 node 节点按访问频度应该在链表中的位置,即 node 节点应该插在 priorNode 节点的后面
    while (priorNode->prior != NULL && priorNode->prior->freq <= node->freq) // 按访问频度,高频在前面,并且在同频的前面
        priorNode = priorNode->prior;
    

    // 4.将 node 节点与 priorNode 节点连接起来
    node->next = priorNode->next;
    priorNode->next->prior = node;
    node->prior = priorNode;
    priorNode->next = node;
    return node;

完整代码:

#include <stdio.h>
#include <malloc.h>

/**
 * 双链表节点结构体
 */
typedef struct DLNode 
    /**
     * 双链表节点数据域
     */
    int data;
    /**
     * 访问频度数据域
     */
    int freq;
    /**
     * 双链表节点的前驱节点,数据域
     */
    struct DLNode *prior;
    /**
     * 双链表节点的后继节点,数据域
     */
    struct DLNode *next;
 DLNode;

/**
 * 通过尾插法创建双链表
 * @param list 双链表
 * @param nums 会放入到双链表中的数据
 * @param n 数组长度
 * @return 创建成功的双链表
 */
DLNode *createByTail(DLNode **list, int nums[], int n) 
    // 1.初始化链表,即创建双链表的头结点,也可以直接调用 init 方法进行初始化
    // 1.1 给双链表头结点分配空间
    *list = (DLNode *) malloc(sizeof(DLNode));
    // 1.2 将双链表的 prior 指针指向 null
    (*list)->prior = NULL;
    // 1.3 将双链表的 next 指针指向 null
    (*list)->next = NULL;

    // 使用尾插法,最重要的是知道链表的尾节点,初始时为链表的头结点
    DLNode *tailNode = *list;

    // 2.循环数组中元素,然后插入到链表的尾部
    for (int i = 0; i < n; i++) 
        // 2.1 创建新节点
        // 2.1.1 为新节点分配空间
        DLNode *newNode = (DLNode *) malloc(sizeof(DLNode));
        // 2.1.2 指定新节点的数据域
        newNode->data = nums[i];
        // 2.1.3 将新节点的 prior 指针指向 null
        newNode->prior = NULL;
        // 2.1.4 将新节点的 next 指针指向 null
        newNode->next = NULL;
        // 2.1.5 将新节点的 freq 数据域初始为 0
        newNode->freq = 0;

        // 2.2 将新节点连接到链表中
        // 2.2.1 将原尾节点的 next 指针指向新节点
        tailNode->next = newNode;
        // 2.2.2 将新节点的 prior 指针指向原尾节点,这时已经将新节点连接到链表的尾部了
        newNode->prior = tailNode;
        // 2.2.3 不要忘记更新尾节点,让新节点成为新的尾节点,才能进行下一次插入
        tailNode = newNode;
    
    return *list;


/**
 * 按访问频度来修改指定值节点在链表中的位置
 * @param list 双链表
 * @param x 指定值
 */
DLNode *locate(DLNode **list, int x) 
    // 1.找到双链表中值为 x 的节点
    // 变量,记录双链表节点用于扫描链表,初始为双链表的第一个节点
    DLNode *node = (*list)->next;
    // 从头到尾扫描双链表所有节点
    while (node != NULL) 
        // 找到节点值等于 x 的节点
        if (node->data == x) 
            break;
        
        node = node->next;
    

    // 2.摘除 node 节点从双链表中
    // 变量,记录 node 节点的前驱节点
    DLNode *priorNode = node->prior;
    // 摘除 node 节点,将 pre 节点与 node 节点的后继节点连接起来
    if (node->next != NULL) 
        node->next->prior = node->prior;
    
    node->prior->next = node->next;

    // 3.修改访问频度并且找到 node 节点在链表中的插入位置
    // 将访问频度增加 1
    node->freq = node->freq + 1;
    // 找到 node 节点按访问频度应该在链表中的位置,即 node 节点应该插在 priorNode 节点的后面
    while (priorNode->prior != NULL && priorNode->prior->freq <= node->freq) // 按访问频度,高频在前面,并且在同频的前面
        priorNode = priorNode->prior;
    

    // 4.将 node 节点与 priorNode 节点连接起来
    node->next = priorNode->next;
    priorNode->next->prior = node;
    node->prior = priorNode;
    priorNode->next = node;
    return node;


/**
 * 打印双链表中所有节点数据
 * @param list 双链表
 */
void print(DLNode *list) 
    // 链表的第一个节点
    DLNode *node = list->next;
    // 循环遍历链表的所有节点
    printf("[");
    while (node != NULL) 
        printf("<%d,%d>", node->data, node->freq);
        if (node->next != NULL) 
            printf(", ");
        
        node = node->next;
    
    printf("]\\n");


int main() 
    // 双链表
    DLNode *list;
    int nums[] = 1, 2, 3, 4, 5;
    int n = 5;
    createByTail(&list, nums, n);
    print(list);

    // 调用函数
    locate(&list, 3);
    print(list);
    locate(&list, 2);
    print(list);
    locate(&list, 4);
    print(list);
    locate(&list, 3);
    print(list);

执行结果:

[<1,0>, <2,0>, <3,0>, <4,0>, <5,0>]
[<3,1>, <1,0>, <2,0>, <4,0>, <5,0>]
[<2,1>, <3,1>, <1,0>, <4,0>, <5,0>]
[<4,1>, <2,1>, <3,1>, <1,0>, <5,0>]
[<3,2>, <4,1>, <2,1>, <1,0>, <5,0>]

Java实现

核心代码:

    /**
     * 按访问频度来修改指定值节点在链表中的位置
     *
     * @param x 指定值
     */
    public DLNode locate(int x) 
        // 1.找到双链表中值为 x 的节点
        // 变量,记录双链表节点用于扫描链表,初始为双链表的第一个节点
        DLNode node = list.next;
        // 从头到尾扫描双链表所有节点
        while (node != null) 
            // 找到节点值等于 x 的节点
            if (node.data == x) 
                break;
            
            node = node.next;
        

        // 2.摘除 node 节点从双链表中
        // 变量,记录 node 节点的前驱节点
        DLNode priorNode = node.prior;
        // 摘除 node 节点,将 pre 节点与 node 节点的后继节点连接起来
        if (node.next != null) 
            node.next.prior = node.prior;
        
        node.prior.next = node.next;

        // 3.修改访问频度并且找到 node 节点在链表中的插入位置
        // 将访问频度增加 1
        node.freq = node.freq + 1;
        // 找到 node 节点按访问频度应该在链表中的位置,即 node 节点应该插在 priorNode 节点的后面
        while (priorNode.prior != null && priorNode.prior.freq <= node.freq) // 按访问频度,高频在前面,并且在同频的前面
            priorNode = priorNode.prior;
        

        // 4.将 node 节点与 priorNode 节点连接起来
        node.next = priorNode.next;
        priorNode.next.prior = node;
        node.prior = priorNode;
        priorNode.next = node;
        return node;
    

完整代码:

public class DoubleLinkedList 
    DLNode list;

    /**
     * 通过尾插法创建双链表
     *
     * @param nums 会放入到双链表中的数据
     * @return 创建成功的双链表
     */
    public DLNode createByTail(int... nums) 
        // 1.初始化链表,即创建双链表的头结点,也可以直接调用 init 方法进行初始化
        // 1.1 给双链表头结点分配空间
        list = new DLNode();
        // 1.2 将双链表的 prior 指针指向 null
        list.prior = null;
        // 1.3 将双链表的 next 指针指向 null
        list.next = null;

        // 使用尾插法,最重要的是知道链表的尾节点,初始时为链表的头结点
        DLNode tailNode = list;

        // 2.循环数组中元素,然后插入到链表的尾部
        for (int i = 0; i < nums.length; i++) 
            // 2.1 创建新节点
            // 2.1.1 为新节点分配空间
            DLNode newNode = new DLNode();
            // 2.1.2 指定新节点的数据域
            newNode.data = nums[i];
            // 2.1.3 将新节点的 prior 指针指向 null
            newNode.prior = null;
            // 2.1.4 将新节点的 next 指针指向 null
            newNode.next = null;
            // 2.1.5 将新节点的 freq 频度置为 0
            newNode.freq = 0;

            // 2.2 将新节点连接到链表中
            // 2.2.1 将原尾节点的 next 指针指向新节点
            tailNode.next = newNode;
            // 2.2.2 将新节点的 prior 指针指向原尾节点,这时已经将新节点连接到链表的尾部了
            newNode.prior = tailNode;
            // 2.2.3 不要忘记更新尾节点,让新节点成为新的尾节点,才能进行下一次插入
            tailNode = newNode;
        
        return list;
    

    /**
     * 按访问频度来修改指定值节点在链表中的位置
     *
     * @param x 指定值
     */
    public DLNode locate(int x) 
        // 1.找到双链表中值为 x 的节点
        // 变量,记录双链表节点用于扫描链表,初始为双链表的第一个节点
        DLNode node = list.next;
        // 从头到尾扫描双链表所有节点
        while (node != null) 
            // 找到节点值等于 x 的节点
            if (node.data == x) 
                break;
            
            node = node.next;
        

        // 2.摘除 node 节点从双链表中
        // 变量,记录 node 节点的前驱节点
        DLNode priorNode = node.prior;
        // 摘除 node 节点,将 pre 节点与 node 节点的后继节点连接起来
        if (node.next != null) 
            node.next.prior = node.prior;
        
        node.prior.next = node.next;

        // 3.修改访问频度并且找到 node 节点在链表中的插入位置
        // 将访问频度增加 1
        node.freq = node.freq + 1;
        // 找到 node 节点按访问频度应该在链表中的位置,即 node 节点应该插在 priorNode 节点的后面
        while (priorNode.prior != null && priorNode.prior.freq <= node.freq) // 按访问频度,高频在前面,并且在同频的前面
            priorNode = priorNode.prior;
        

        // 4.将 node 节点与 priorNode 节点连接起来
        node.next = priorNode.next;
        priorNode.next.prior = node;
        node.prior = priorNode;
        priorNode.next = node;
        return node;
    

    /**
     * 打印双链表中所有节点数据
     */
    public void print() 
        // 链表的第一个节点
        DLNode node = list.next;
        // 循环遍历链表的所有节点
        String str = "[";
        while (node != null) 
            str += "<" + node.data + "," + node.freq + ">";
            if (node.next != null) 
                str += ", ";
            
            node = node.next;
        
        str += "]";
        // 打印结果
        System.out.println(str);
    



/**
 * 双链表节点,包括一个数据域和两个指针域(分别指向前驱节点和后继节点)
 */
class DLNode 
    /**
     * 双链表节点的数据域
     */
    int data;
    /**
     * 双链表节点的访问频度
     */
    int freq;
    /**
     * 双链表节点的前驱节点
     */
    DLNode prior;
    /**
     * 双链表节点的后继节点
     */
    DLNode next;

测试代码:

public class DoubleLinkedListTest 
    public static void main(String[] args) throws Exception 
        // 双链表
        DoubleLinkedList list = new DoubleLinkedList();
        list.createByTail(1, 2, 3, 4, 5);
        list.print();

        // 调用函数
        list.locate(3);
        list.print();
        list.locate(2);
        list.print();
        list.locate(4);
        list.print();
        list.locate(3);
        list.print();
    

执行结果:

[<1,0>, <2,0>, <3,0>, <4,0>, <5,0>]
[<3,1>, <1,0>, <2,0>, <4,0>, <5,0>]
[<2,1>, <3,1>, <1,0>, <4,0>, <5,0>]
[<4,1>, <2,1>, <3,1>, <1,0>, <5,0>]
[<3,2>, <4,1>, <2,1>, <1,0>, <5,0>]

以上是关于线性表练习之Example046-使双链表中结点保持按访问频度非增(递减)的顺序排列,同时最近访问的结点排在频度相同的结点前面的主要内容,如果未能解决你的问题,请参考以下文章

线性表练习之Example045-有一个带头结点的单链表 L,设计一个算法使其元素递增有序

线性表练习之Example037-判断带头节点的循环双链表是否对称

线性表练习之Example019-删除单链表中所有介于给定两个值之间的元素的元素

线性表练习之Example042-设计一个递归算法,删除不带头结点的单链表 L 中所有值为 x 的结点

线性表练习之Example015-求单链表倒数第 k 个节点

线性表练习之Example032-将一个带头结点的单链表 A 分解成两个单链表 A 和 B,其中 A 表只包含原表中序号为奇数的元素,B 表中只包含原表中序号为偶数的元素