算法导论 第11章 散列表 11.1 直接寻址表 习题解答

文章介绍了如何使用直接寻址表和位向量高效地表示动态集合,包括查找最大元素、位向量表示无卫星数据集合以及在大数组上实现直接寻址字典的方法,保证了SEARCH、INSERT和DELETE操作的O(1)时间复杂度。
摘要由CSDN通过智能技术生成

11.1-1 假设一动态集合S用一个长度为m的直接寻址表T来表示。请给出一个查找S中最大元素的过程。你所给的过程在最坏情况下的运行时间是多少?

如果我们用一个直接寻址表T来表示动态集合S,那么每个元素的关键字值就是其在表中的下标。假设元素的关键字是整数,我们可以通过直接寻址表来找到最大元素。

查找S中最大元素的过程很简单,只需遍历直接寻址表T,找到最大的非空槽。具体过程如下:

  1. 初始化一个变量max_index为-1,表示当前找到的最大元素在表中的下标。
  2. 遍历直接寻址表T的所有槽,从0到m-1。
  3. 对于每个非空槽T[i],如果max_index为-1或者T[i]的关键字值大于T[max_index]的关键字值,则更新max_index为i。
  4. 返回T[max_index]作为S中的最大元素。

在最坏情况下,需要遍历整个直接寻址表T,找到最大元素。因此,最坏情况下的运行时间是O(m),其中m是直接寻址表的长度。这是一个线性时间复杂度的算法。

public class MaximumElementFinder {

    public static int findMaximumElement(int[] T) {
        int maxIndex = -1;

        // 遍历直接寻址表T的所有槽
        for (int i = 0; i < T.length; i++) {
            // 对于每个非空槽T[i]
            if (T[i] != 0) {
                // 如果maxIndex为-1或者T[i]的关键字值大于T[maxIndex]的关键字值
                if (maxIndex == -1 || T[i] > T[maxIndex]) {
                    // 更新maxIndex为i
                    maxIndex = i;
                }
            }
        }

        // 返回T[maxIndex]作为S中的最大元素
        return (maxIndex != -1) ? T[maxIndex] : -1; // 返回-1表示集合为空
    }

    public static void main(String[] args) {
        // 示例使用一个长度为m的直接寻址表
        int m = 10;
        int[] directAddressTable = new int[m];

        // 插入一些元素
        directAddressTable[2] = 15;
        directAddressTable[5] = 30;
        directAddressTable[8] = 25;

        // 查找最大元素
        int maximumElement = findMaximumElement(directAddressTable);

        // 输出结果
        if (maximumElement != -1) {
            System.out.println("Maximum Element in the set: " + maximumElement);
        } else {
            System.out.println("The set is empty.");
        }
    }
}

11.1-2 位向量(bit vector)是一个仅包含0和1的数组。长度为m的位向量所占空间要比包含m个指针的数组少得多。请说明如何用一个位向量来表示一个包含不同元素(无卫星数据)的动态集合。字典操作的运行时间应为O(1)。

使用位向量来表示一个包含不同元素的动态集合,并使字典操作的运行时间为 O(1) 是通过使用哈希表的思想实现的。

  1. 初始化位向量: 假设动态集合中元素的关键字是整数,我们可以将每个整数对应到位向量的一个位置上。如果某个整数在动态集合中,将对应位置的位设置为1,否则为0。
  2. 插入元素: 要插入一个元素,只需将该元素对应的位设置为1。
  3. 删除元素: 要删除一个元素,只需将该元素对应的位设置为0。
  4. 查找元素: 要查找一个元素是否在集合中,只需检查对应位置的位是否为1。

通过这种方法,我们可以实现字典操作(插入、删除、查找)的运行时间为 O(1)。因为对于任何元素,我们都可以直接计算其在位向量中的位置,并在常数时间内进行相应的操作。需要注意的是,这种方法的适用性受到位向量长度的限制。如果元素的关键字范围很大,可能需要较大的位向量,这会占用较多的空间。

import java.util.BitSet;

public class DynamicSetWithBitVector {
    private BitSet bitVector;

    public DynamicSetWithBitVector(int universeSize) {
        bitVector = new BitSet(universeSize);
    }

    // 插入元素
    public void insert(int key) {
        bitVector.set(key);
        System.out.println("Inserted element with key " + key);
    }

    // 删除元素
    public void delete(int key) {
        bitVector.clear(key);
        System.out.println("Deleted element with key " + key);
    }

    // 查找元素
    public boolean search(int key) {
        boolean exists = bitVector.get(key);
        System.out.println("Element with key " + key + " exists: " + exists);
        return exists;
    }

    public static void main(String[] args) {
        int universeSize = 1000; // 假设关键字的范围为0到999
        DynamicSetWithBitVector dynamicSet = new DynamicSetWithBitVector(universeSize);

        // 插入操作
        dynamicSet.insert(5);
        dynamicSet.insert(10);

        // 查找操作
        dynamicSet.search(5); // 应返回 true
        dynamicSet.search(7); // 应返回 false,因为7没有被插入

        // 删除操作
        dynamicSet.delete(10);

        // 再次查找
        dynamicSet.search(10); // 应返回 false,因为10已被删除
    }
}

(在计算机科学和数据结构的上下文中,卫星数据通常指与关键字相关联的附加信息。在一个集合或字典中,关键字是用来唯一标识元素的值,而卫星数据则是与这个关键字关联的其他信息。

举例来说,考虑一个包含学生信息的动态集合,其中每个学生记录包括学生的学号作为关键字,而与之相关联的姓名、成绩、出生日期等信息就是卫星数据。在某些情况下,我们可能只关心关键字,而在其他情况下,我们可能需要同时访问关键字和与之相关联的卫星数据。

在位向量的上下文中,由于题目中提到“无卫星数据”,意味着我们只关心元素是否存在于集合中,而不需要与之关联额外的信息。这样的情况下,位向量中的每个位仅用于表示元素的存在或不存在,而不存储与之相关的卫星数据。)

11.1-3 试说明如何实现一个直接寻址表,表中各元素的关键字不必都不相同,且各元素可以有卫星数据。所有三种字典操作(INSERT、DELETE和SEARCH)的运行时间应为O(1)。(不要忘记DELETE要处理的是被删除对象的指针变量,而不是关键字。)

在直接寻址表中,可以将其视为一个链表的数组,其中数组的每个槽都为每个关键字维护一个双向链表,每个节点都是该链表中的元素,包含了关键字对应的卫星数据,并通过双向指针连接到链表中的前一个和后一个节点。这样可以以常数时间复杂度实现插入、删除和查找操作。

  1. 插入(INSERT):要插入具有特定关键字的新元素,你可以创建一个包含卫星数据的新节点,并将其附加到对应关键字的双向链表中。这个操作的时间复杂度是常数,因为只涉及更新链表中的指针。
  2. 删除(DELETE):要删除具有特定关键字的元素,你需要定位双向链表中的节点,并调整前一个和后一个节点的指针,绕过待删除的节点。这个操作也是常数时间复杂度,因为只涉及在链表中调整指针。
  3. 查找(SEARCH):要查找具有特定关键字的元素,直接返回对应关键字的双向链表的头节点即可。这个操作的时间复杂度是常数,因为直接访问链表的头节点。
class Node {
    int key;
    String satelliteData;
    Node prev;
    Node next;

    public Node(int key, String satelliteData) {
        this.key = key;
        this.satelliteData = satelliteData;
    }
}

public class DirectAddressTableWithLinkedList {
    private Node[] table;

    public DirectAddressTableWithLinkedList(int universeSize) {
        table = new Node[universeSize];
    }

    public void insert(int key, String satelliteData) {
        Node newNode = new Node(key, satelliteData);
        if (table[key] == null) {
            table[key] = newNode;
        } else {
            Node current = table[key];
            while (current.next != null) {
                current = current.next;
            }
            current.next = newNode;
            newNode.prev = current;
        }
        System.out.println("Inserted: Key " + key + ", Satellite Data: " + satelliteData);
    }

    public void delete(int key) {
        if (table[key] != null) {
            Node nodeToDelete = table[key];
            table[key] = null; // 将表中的指针置为空

            // 调整前一个节点的指针
            if (nodeToDelete.prev != null) {
                nodeToDelete.prev.next = nodeToDelete.next;
            }

            // 调整后一个节点的指针
            if (nodeToDelete.next != null) {
                nodeToDelete.next.prev = nodeToDelete.prev;
            }

            System.out.println("Deleted: Key " + key);
        } else {
            System.out.println("Key " + key + " not found. Nothing to delete.");
        }
    }

    public String search(int key) {
        if (table[key] != null) {
            return table[key].satelliteData;
        } else {
            return null;
        }
    }

    public static void main(String[] args) {
        DirectAddressTableWithLinkedList dat = new DirectAddressTableWithLinkedList(1000);

        // INSERT操作
        dat.insert(5, "Satellite Data for Key 5");
        dat.insert(10, "Satellite Data for Key 10");

        // SEARCH操作
        String result_5 = dat.search(5);
        String result_10 = dat.search(10);
        String result_15 = dat.search(15);

        System.out.println("Search Result for Key 5: " + result_5); // 输出 "Satellite Data for Key 5"
        System.out.println("Search Result for Key 10: " + result_10); // 输出 "Satellite Data for Key 10"
        System.out.println("Search Result for Key 15: " + result_15); // 输出 null,因为15没有被插入

        // DELETE操作
        dat.delete(5);
        String result_after_delete = dat.search(5);

        System.out.println("Search Result for Key 5 after deletion: " + result_after_delete); // 输出 null,因为关键字5已经被删除
    }
}

11.1-4 我们希望在一个非常大的数组上,通过利用直接寻址的方式来实现一个字典。开始时,该数组中可能包含一些无用信息,但要对整个数组进行初始化是不太实际的,因为该数组的规模太大。请给出大数组上实现直接寻址字典的方案。每个存储对象占用O(1)空间:SEARCH、INSERT和DELETE操作的时间均为O(1);并且对数据结构初始化的时间为O(1)。(提示:可以利用一个附加数组,处理方式类似于栈,其大小等于世纪存储在字典中的关键字数目,以帮助确定大数组中某个给定的项是否有效。)

利用一个附加的辅助数组和一个类似栈的处理方式。辅助数组起到了标记主数组中哪些位置是有效的作用。这种方案可以实现SEARCH、INSERT和DELETE操作的时间复杂度均为O(1),并且对数据结构的初始化时间也是O(1)。

  1. 主数组: 使用一个非常大的主数组 A 来表示字典。该数组可能包含一些无用信息。
  2. 辅助数组: 使用一个辅助数组 valid,其大小等于字典中实际存储的关键字数量。该数组的作用是标记主数组中某个位置是否有效。
  3. 初始化: 对于字典的初始化,只需将 valid 数组的元素全部置为无效状态。这个操作的时间复杂度是O(1)。
  4. SEARCH操作: 对于SEARCH操作,直接通过 valid 数组判断主数组中某个位置是否有效。如果 valid[i] 为真,表示该位置有效,可以执行搜索操作。时间复杂度为O(1)。
  5. INSERT操作: 对于INSERT操作,首先找到主数组中的一个无效位置,然后在 valid 数组中标记该位置为有效。这个操作的时间复杂度也是O(1)。
  6. DELETE操作: 对于DELETE操作,直接将 valid 数组中对应位置标记为无效即可。同样,这个操作的时间复杂度是O(1)。
public class DirectAddressDictionary {

    private boolean[] valid; // 辅助数组,标记主数组中哪些位置是有效的

    public DirectAddressDictionary(int size) {
        valid = new boolean[size];
        // 初始化,将所有位置标记为无效
        for (int i = 0; i < size; i++) {
            valid[i] = false;
        }
    }

    // SEARCH操作
    public boolean search(int index) {
        // 直接通过valid数组判断主数组中某个位置是否有效
        return index >= 0 && index < valid.length && valid[index];
    }

    // INSERT操作
    public void insert(int index) {
        // 找到主数组中的一个无效位置,标记为有效
        if (index >= 0 && index < valid.length && !valid[index]) {
            valid[index] = true;
        }
    }

    // DELETE操作
    public void delete(int index) {
        // 直接将valid数组中对应位置标记为无效
        if (index >= 0 && index < valid.length) {
            valid[index] = false;
        }
    }

    public static void main(String[] args) {
        int arraySize = 1000;
        DirectAddressDictionary dictionary = new DirectAddressDictionary(arraySize);

        // 示例操作
        int indexToSearch = 5;
        System.out.println("Is index " + indexToSearch + " valid? " + dictionary.search(indexToSearch));

        dictionary.insert(5);
        System.out.println("Is index " + indexToSearch + " valid after insertion? " + dictionary.search(indexToSearch));

        dictionary.delete(5);
        System.out.println("Is index " + indexToSearch + " valid after deletion? " + dictionary.search(indexToSearch));
    }
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值