为了账号安全,请及时绑定邮箱和手机立即绑定

数据结构高级:新手入门教程

概述

本文深入探讨了数据结构高级特性,包括自平衡树如AVL树和红黑树,以及哈希表的实现与应用。文章还提供了多种数据结构的实例代码和常见问题的解决方案。此外,文中还介绍了时间复杂度和空间复杂度的优化技巧,并推荐了进一步学习的数据结构资源。数据结构高级涵盖了多种复杂的数据结构和优化方法。

数据结构基础回顾

基本概念介绍

数据结构是计算机科学中的一个核心概念,它涉及到数据的组织、管理和存储。理解数据结构的基本概念和类型对于编写高效的程序至关重要。数据结构不仅仅是用来存储数据,它们还提供了访问和操作数据的方式。以下是一些基本概念:

  1. 数据类型:数据类型定义了数据的种类,例如整型(int)、浮点型(float)、字符型(char)等。
  2. 变量:变量是用于存储数据的标识符。变量可以被赋予不同的值,并可以在程序中被多次引用。
  3. 数组:数组是一种数据结构,它允许你存储多个相同类型的数据项。数组中的每个元素可以通过索引来访问。
# 变量示例
x = 10
y = 5.5
name = "Alice"

# 数组示例
arr = [1, 2, 3, 4, 5]
print(arr[0])  # 输出:1

常见数据结构类型

数据结构按其逻辑结构可分为以下几种类型:

  1. 数组:序列中的元素通过索引进行访问。数组中的元素是连续存储的。
  2. 链表:链表是由一系列节点(Node)组成的,每个节点包含数据部分和一个指向另一个节点的指针。
# 链表示例
class Node:
    def __init__(self, data):
        self.data = data
        self.next = None

# 创建链表
head = Node(1)
second = Node(2)
third = Node(3)

head.next = second
second.next = third

# 打印链表
current = head
while current:
    print(current.data)
    current = current.next
高级数据结构概述

树(二叉树、平衡树等)

树是一种非线性数据结构,它是由多个节点组成的,每个节点最多只有一个父节点,但可以有多个子节点。常见的树类型包括二叉树、平衡树等。

二叉树

二叉树是一种树结构,在这种结构中,每个节点最多只有两个子节点,通常称为左子节点和右子节点。

class TreeNode:
    def __init__(self, data):
        self.data = data
        self.left = None
        self.right = None

# 创建二叉树
root = TreeNode(1)
root.left = TreeNode(2)
root.right = TreeNode(3)
root.left.left = TreeNode(4)
root.left.right = TreeNode(5)

# 打印二叉树
def print_tree(node):
    if node is not None:
        print(node.data)
        print_tree(node.left)
        print_tree(node.right)

print_tree(root)

平衡树

平衡树是一种特殊的树结构,它确保任何路径从根到叶子的长度不会差太多。常见的平衡树类型有AVL树和红黑树。

图(有向图、无向图等)

图是一种数据结构,由节点(Vertex)和边(Edge)组成。边可以是有向的(有方向性)或无向的(没有方向性)。

有向图

有向图中的边是有方向的。

# 有向图表示
class Graph:
    def __init__(self):
        self.graph = {}

    def add_vertex(self, vertex):
        if vertex not in self.graph:
            self.graph[vertex] = []

    def add_edge(self, vertex1, vertex2):
        self.graph[vertex1].append(vertex2)

# 创建有向图
g = Graph()
g.add_vertex('A')
g.add_vertex('B')
g.add_vertex('C')
g.add_edge('A', 'B')
g.add_edge('B', 'C')
g.add_edge('C', 'A')

# 打印有向图
print(g.graph)

无向图

无向图中的边是没有方向的。

# 无向图表示
class Graph:
    def __init__(self):
        self.graph = {}

    def add_vertex(self, vertex):
        if vertex not in self.graph:
            self.graph[vertex] = []

    def add_edge(self, vertex1, vertex2):
        self.graph[vertex1].append(vertex2)
        self.graph[vertex2].append(vertex1)

# 创建无向图
g = Graph()
g.add_vertex('A')
g.add_vertex('B')
g.add_vertex('C')
g.add_edge('A', 'B')
g.add_edge('B', 'C')
g.add_edge('C', 'A')

# 打印无向图
print(g.graph)

有向图的应用:最短路径算法

下面是一个简单的有向图最短路径算法示例,使用Dijkstra算法。

import sys

class Graph:
    def __init__(self, num_vertices):
        self.num_vertices = num_vertices
        self.edges = [[0] * num_vertices for _ in range(num_vertices)]

    def add_edge(self, v1, v2, weight):
        self.edges[v1][v2] = weight
        self.edges[v2][v1] = weight

    def dijkstra(self, start_vertex):
        D = {v: float('inf') for v in range(self.num_vertices)}
        D[start_vertex] = 0
        visited = set()
        while len(visited) < self.num_vertices:
            min_vertex = None
            min_value = float('inf')
            for v in range(self.num_vertices):
                if v not in visited and D[v] < min_value:
                    min_vertex = v
                    min_value = D[v]
            visited.add(min_vertex)
            for v in range(self.num_vertices):
                if self.edges[min_vertex][v] != 0 and v not in visited:
                    new_dist = D[min_vertex] + self.edges[min_vertex][v]
                    if new_dist < D[v]:
                        D[v] = new_dist
        return D

# 创建图
g = Graph(5)
g.add_edge(0, 1, 1)
g.add_edge(0, 2, 2)
g.add_edge(1, 3, 4)
g.add_edge(2, 3, 3)
g.add_edge(2, 4, 3)

# 计算最短路径
distances = g.dijkstra(0)
print(distances)  # 输出:{0: 0, 1: 1, 2: 2, 3: 5, 4: 5}
数据结构高级特性详解

自平衡树(AVL树、红黑树等)

自平衡树是一种能够自动保持平衡的树结构,确保树的高度保持在最小范围内,从而保证操作的时间复杂度。

AVL树

AVL树是一种自平衡二叉查找树,它确保任何节点的两个子树的高度差最多为1。

class TreeNode:
    def __init__(self, data):
        self.data = data
        self.left = None
        self.right = None
        self.height = 1

def get_height(node):
    if not node:
        return 0
    return node.height

def get_balance(node):
    if not node:
        return 0
    return get_height(node.left) - get_height(node.right)

def left_rotate(node):
    new_root = node.right
    node.right = new_root.left
    new_root.left = node
    node.height = 1 + max(get_height(node.left), get_height(node.right))
    new_root.height = 1 + max(get_height(new_root.left), get_height(new_root.right))
    return new_root

def right_rotate(node):
    new_root = node.left
    node.left = new_root.right
    new_root.right = node
    node.height = 1 + max(get_height(node.left), get_height(node.right))
    new_root.height = 1 + max(get_height(new_root.left), get_height(new_root.right))
    return new_root

def insert(node, data):
    if not node:
        return TreeNode(data)
    if data < node.data:
        node.left = insert(node.left, data)
    elif data > node.data:
        node.right = insert(node.right, data)
    else:
        return node

    node.height = 1 + max(get_height(node.left), get_height(node.right))
    balance = get_balance(node)

    if balance > 1 and data < node.left.data:
        return right_rotate(node)
    if balance < -1 and data > node.right.data:
        return left_rotate(node)
    if balance > 1 and data > node.left.data:
        node.left = left_rotate(node.left)
        return right_rotate(node)
    if balance < -1 and data < node.right.data:
        node.right = right_rotate(node.right)
        return left_rotate(node)

    return node

def inorder_traversal(node):
    if node:
        inorder_traversal(node.left)
        print(node.data, end=' ')
        inorder_traversal(node.right)

# 创建AVL树
root = None
root = insert(root, 10)
root = insert(root, 20)
root = insert(root, 30)
root = insert(root, 40)
root = insert(root, 50)
root = insert(root, 25)

# 打印AVL树
inorder_traversal(root)

红黑树

红黑树是一种自平衡二叉查找树,它通过颜色属性(红色或黑色)来实现平衡。

class TreeNode:
    def __init__(self, data):
        self.data = data
        self.parent = None
        self.left = None
        self.right = None
        self.color = "RED"

class RedBlackTree:
    def __init__(self):
        self.TNULL = TreeNode(0)
        self.TNULL.color = "BLACK"
        self.root = self.TNULL

    def insert(self, key):
        new_node = TreeNode(key)
        new_node.left = self.TNULL
        new_node.right = self.TNULL
        parent = None
        current = self.root
        while current != self.TNULL:
            parent = current
            if new_node.data < current.data:
                current = current.left
            else:
                current = current.right
        new_node.parent = parent
        if not parent:
            self.root = new_node
        elif new_node.data < parent.data:
            parent.left = new_node
        else:
            parent.right = new_node
        new_node.color = "RED"
        self.fix_insert(new_node)

    def fix_insert(self, k):
        while k.parent.color == "RED":
            if k.parent == k.parent.parent.right:
                u = k.parent.parent.left
                if u.color == "RED":
                    u.color = "BLACK"
                    k.parent.color = "BLACK"
                    k.parent.parent.color = "RED"
                    k = k.parent.parent
                else:
                    if k == k.parent.left:
                        k = k.parent
                        self.right_rotate(k)
                    k.parent.color = "BLACK"
                    k.parent.parent.color = "RED"
                    self.left_rotate(k.parent.parent)
            else:
                u = k.parent.parent.right
                if u.color == "RED":
                    u.color = "BLACK"
                    k.parent.color = "BLACK"
                    k.parent.parent.color = "RED"
                    k = k.parent.parent
                else:
                    if k == k.parent.right:
                        k = k.parent
                        self.left_rotate(k)
                    k.parent.color = "BLACK"
                    k.parent.parent.color = "RED"
                    self.right_rotate(k.parent.parent)
        self.root.color = "BLACK"

    def left_rotate(self, x):
        y = x.right
        x.right = y.left
        if y.left != self.TNULL:
            y.left.parent = x
        y.parent = x.parent
        if x.parent == None:
            self.root = y
        elif x == x.parent.left:
            x.parent.left = y
        else:
            x.parent.right = y
        y.left = x
        x.parent = y

    def right_rotate(self, x):
        y = x.left
        x.left = y.right
        if y.right != self.TNULL:
            y.right.parent = x
        y.parent = x.parent
        if x.parent == None:
            self.root = y
        elif x == x.parent.right:
            x.parent.right = y
        else:
            x.parent.left = y
        y.right = x
        x.parent = y

    def search(self, key):
        current = self.root
        while current != self.TNULL:
            if key == current.data:
                return True
            elif key < current.data:
                current = current.left
            else:
                current = current.right
        return False

# 创建红黑树
rbt = RedBlackTree()
rbt.insert(10)
rbt.insert(20)
rbt.insert(30)
rbt.insert(40)
rbt.insert(50)
rbt.insert(25)

# 搜索红黑树
print(rbt.search(25))  # 输出:True
print(rbt.search(26))  # 输出:False

哈希表及其应用

哈希表是一种数据结构,它使用哈希函数将键映射到地址,从而提供快速的查找、插入和删除操作。

class HashTable:
    def __init__(self, capacity=100):
        self.capacity = capacity
        self.size = 0
        self.buckets = [None] * capacity

    def _hash(self, key):
        return hash(key) % self.capacity

    def insert(self, key, value):
        index = self._hash(key)
        while self.buckets[index]:
            if self.buckets[index][0] == key:
                self.buckets[index] = (key, value)
                return
            index = (index + 1) % self.capacity
        self.buckets[index] = (key, value)
        self.size += 1

    def get(self, key):
        index = self._hash(key)
        while self.buckets[index]:
            if self.buckets[index][0] == key:
                return self.buckets[index][1]
            index = (index + 1) % self.capacity
        return None

    def delete(self, key):
        index = self._hash(key)
        while self.buckets[index]:
            if self.buckets[index][0] == key:
                self.buckets[index] = None
                self.size -= 1
                return
            index = (index + 1) % self.capacity

# 创建哈希表
hash_table = HashTable()
hash_table.insert('apple', 10)
hash_table.insert('banana', 20)
hash_table.insert('cherry', 30)

# 操作哈希表
print(hash_table.get('apple'))  # 输出:10
print(hash_table.get('banana'))  # 输出:20
print(hash_table.get('cherry'))  # 输出:30
hash_table.delete('apple')
print(hash_table.get('apple'))  # 输出:None

哈希表的应用:缓存实现

下面是一个简单的哈希表缓存实现示例,用于存储和检索频繁访问的数据。

class Cache:
    def __init__(self, capacity=100):
        self.capacity = capacity
        self.size = 0
        self.cache = {}

    def get(self, key):
        if key in self.cache:
            return self.cache[key]
        return None

    def set(self, key, value):
        if self.size == self.capacity:
            del self.cache[next(iter(self.cache))]
            self.size -= 1
        self.cache[key] = value
        self.size += 1

# 创建缓存
cache = Cache(5)
cache.set('apple', 10)
cache.set('banana', 20)
cache.set('cherry', 30)
cache.set('date', 40)
cache.set('elderberry', 50)
cache.set('fig', 60)  # 超出容量,会替换最早存储的数据

# 操作缓存
print(cache.get('apple'))  # 输出:10
print(cache.get('banana'))  # 输出:20
print(cache.get('cherry'))  # 输出:30
print(cache.get('date'))  # 输出:40
print(cache.get('elderberry'))  # 输出:50
print(cache.get('fig'))  # 输出:60
实战演练

实例代码解析

实例代码一:二叉查找树

class TreeNode:
    def __init__(self, data):
        self.data = data
        self.left = None
        self.right = None

class BinarySearchTree:
    def __init__(self):
        self.root = None

    def insert(self, data):
        if not self.root:
            self.root = TreeNode(data)
        else:
            self._insert(self.root, data)

    def _insert(self, node, data):
        if data < node.data:
            if node.left:
                self._insert(node.left, data)
            else:
                node.left = TreeNode(data)
        else:
            if node.right:
                self._insert(node.right, data)
            else:
                node.right = TreeNode(data)

    def traverse_inorder(self):
        if self.root:
            self._traverse_inorder(self.root)

    def _traverse_inorder(self, node):
        if node.left:
            self._traverse_inorder(node.left)
        print(node.data, end=' ')
        if node.right:
            self._traverse_inorder(node.right)

# 创建二叉查找树
bst = BinarySearchTree()
bst.insert(10)
bst.insert(20)
bst.insert(30)
bst.insert(40)
bst.insert(50)
bst.insert(25)

# 打印二叉查找树
bst.traverse_inorder()  # 输出:10 20 25 30 40 50

实例代码二:图的最短路径算法

import sys

class Graph:
    def __init__(self, num_vertices):
        self.num_vertices = num_vertices
        self.edges = [[0] * num_vertices for _ in range(num_vertices)]

    def add_edge(self, v1, v2, weight):
        self.edges[v1][v2] = weight
        self.edges[v2][v1] = weight

    def dijkstra(self, start_vertex):
        D = {v: float('inf') for v in range(self.num_vertices)}
        D[start_vertex] = 0
        visited = set()
        while len(visited) < self.num_vertices:
            min_vertex = None
            min_value = float('inf')
            for v in range(self.num_vertices):
                if v not in visited and D[v] < min_value:
                    min_vertex = v
                    min_value = D[v]
            visited.add(min_vertex)
            for v in range(self.num_vertices):
                if self.edges[min_vertex][v] != 0 and v not in visited:
                    new_dist = D[min_vertex] + self.edges[min_vertex][v]
                    if new_dist < D[v]:
                        D[v] = new_dist
        return D

# 创建图
g = Graph(5)
g.add_edge(0, 1, 1)
g.add_edge(0, 2, 2)
g.add_edge(1, 3, 4)
g.add_edge(2, 3, 3)
g.add_edge(2, 4, 3)

# 计算最短路径
distances = g.dijkstra(0)
print(distances)  # 输出:{0: 0, 1: 1, 2: 2, 3: 5, 4: 5}

常见问题解决方法

问题一:查找哈希表中的元素

class HashTable:
    def __init__(self, capacity=100):
        self.capacity = capacity
        self.size = 0
        self.buckets = [None] * capacity

    def _hash(self, key):
        return hash(key) % self.capacity

    def insert(self, key, value):
        index = self._hash(key)
        while self.buckets[index]:
            if self.buckets[index][0] == key:
                self.buckets[index] = (key, value)
                return
            index = (index + 1) % self.capacity
        self.buckets[index] = (key, value)
        self.size += 1

    def get(self, key):
        index = self._hash(key)
        while self.buckets[index]:
            if self.buckets[index][0] == key:
                return self.buckets[index][1]
            index = (index + 1) % self.capacity
        return None

# 创建哈希表
hash_table = HashTable()
hash_table.insert('apple', 10)
hash_table.insert('banana', 20)
hash_table.insert('cherry', 30)

# 查找哈希表中的元素
print(hash_table.get('apple'))  # 输出:10

问题二:二叉查找树的插入和删除

class TreeNode:
    def __init__(self, data):
        self.data = data
        self.left = None
        self.right = None

class BinarySearchTree:
    def __init__(self):
        self.root = None

    def insert(self, data):
        if not self.root:
            self.root = TreeNode(data)
        else:
            self._insert(self.root, data)

    def _insert(self, node, data):
        if data < node.data:
            if node.left:
                self._insert(node.left, data)
            else:
                node.left = TreeNode(data)
        else:
            if node.right:
                self._insert(node.right, data)
            else:
                node.right = TreeNode(data)

    def delete(self, data):
        if not self.root:
            return
        if data < self.root.data:
            self._delete(self.root.left, data)
        elif data > self.root.data:
            self._delete(self.root.right, data)
        else:
            self._delete(self.root, data)

    def _delete(self, node, data):
        if not node:
            return
        if data < node.data:
            self._delete(node.left, data)
        elif data > node.data:
            self._delete(node.right, data)
        else:
            if not node.left and not node.right:
                node = None
            elif not node.left:
                node = node.right
            elif not node.right:
                node = node.left
            else:
                min_right = self._find_min(node.right)
                node.data = min_right.data
                self._delete(node.right, min_right.data)

    def _find_min(self, node):
        while node.left:
            node = node.left
        return node

    def traverse_inorder(self):
        if self.root:
            self._traverse_inorder(self.root)

    def _traverse_inorder(self, node):
        if node.left:
            self._traverse_inorder(node.left)
        print(node.data, end=' ')
        if node.right:
            self._traverse_inorder(node.right)

# 创建二叉查找树
bst = BinarySearchTree()
bst.insert(10)
bst.insert(20)
bst.insert(30)
bst.insert(40)
bst.insert(50)
bst.insert(25)

# 打印二叉查找树
bst.traverse_inorder()  # 输出:10 20 25 30 40 50
bst.delete(25)
bst.traverse_inorder()  # 输出:10 20 30 40 50
数据结构优化技巧

时间复杂度和空间复杂度优化

时间复杂度和空间复杂度是衡量算法性能的重要指标。时间复杂度表示算法执行时间随输入规模增长的变化趋势,空间复杂度表示算法运行时所需额外空间的大小。

时间复杂度优化

时间复杂度优化通常通过改进算法结构或选择更高效的数据结构实现。例如,使用哈希表可以在O(1)时间复杂度内完成查找操作,而使用链表则需要O(n)时间复杂度。

class HashTable:
    def __init__(self, capacity=100):
        self.capacity = capacity
        self.size = 0
        self.buckets = [None] * capacity

    def _hash(self, key):
        return hash(key) % self.capacity

    def insert(self, key, value):
        index = self._hash(key)
        while self.buckets[index]:
            if self.buckets[index][0] == key:
                self.buckets[index] = (key, value)
                return
            index = (index + 1) % self.capacity
        self.buckets[index] = (key, value)
        self.size += 1

    def get(self, key):
        index = self._hash(key)
        while self.buckets[index]:
            if self.buckets[index][0] == key:
                return self.buckets[index][1]
            index = (index + 1) % self.capacity
        return None

# 创建哈希表
hash_table = HashTable()
hash_table.insert('apple', 10)
hash_table.insert('banana', 20)
hash_table.insert('cherry', 30)

# 查找哈希表中的元素
print(hash_table.get('apple'))  # 输出:10

空间复杂度优化

空间复杂度优化通常通过减少不必要的空间占用或选择更紧凑的数据结构实现。例如,使用链表的哈希表相比使用数组的哈希表,空间占用更少。

class Node:
    def __init__(self, data):
        self.data = data
        self.next = None

class LinkedList:
    def __init__(self):
        self.head = None

    def insert(self, data):
        new_node = Node(data)
        if not self.head:
            self.head = new_node
        else:
            current = self.head
            while current.next:
                current = current.next
            current.next = new_node

    def traverse(self):
        current = self.head
        while current:
            print(current.data, end=' ')
            current = current.next

# 创建链表
linked_list = LinkedList()
linked_list.insert(10)
linked_list.insert(20)
linked_list.insert(30)

# 打印链表
linked_list.traverse()  # 输出:10 20 30

数据结构选择与性能分析

选择合适的数据结构对于实现高效算法至关重要。不同的数据结构适用于不同的场景,因此在选择数据结构时需要考虑操作的复杂度和需求。

场景一:查找操作

对于频繁进行查找操作的场景,哈希表通常是最佳选择,因为它可以在常数时间内完成查找。

class HashTable:
    def __init__(self, capacity=100):
        self.capacity = capacity
        self.size = 0
        self.buckets = [None] * capacity

    def _hash(self, key):
        return hash(key) % self.capacity

    def insert(self, key, value):
        index = self._hash(key)
        while self.buckets[index]:
            if self.buckets[index][0] == key:
                self.buckets[index] = (key, value)
                return
            index = (index + 1) % self.capacity
        self.buckets[index] = (key, value)
        self.size += 1

    def get(self, key):
        index = self._hash(key)
        while self.buckets[index]:
            if self.buckets[index][0] == key:
                return self.buckets[index][1]
            index = (index + 1) % self.capacity
        return None

# 创建哈希表
hash_table = HashTable()
hash_table.insert('apple', 10)
hash_table.insert('banana', 20)
hash_table.insert('cherry', 30)

# 查找哈希表中的元素
print(hash_table.get('apple'))  # 输出:10

场景二:排序操作

对于需要进行排序操作的场景,二叉查找树是一种合适的选择,因为它可以在O(log n)时间复杂度内完成插入和查找操作。

class TreeNode:
    def __init__(self, data):
        self.data = data
        self.left = None
        self.right = None

class BinarySearchTree:
    def __init__(self):
        self.root = None

    def insert(self, data):
        if not self.root:
            self.root = TreeNode(data)
        else:
            self._insert(self.root, data)

    def _insert(self, node, data):
        if data < node.data:
            if node.left:
                self._insert(node.left, data)
            else:
                node.left = TreeNode(data)
        else:
            if node.right:
                self._insert(node.right, data)
            else:
                node.right = TreeNode(data)

    def traverse_inorder(self):
        if self.root:
            self._traverse_inorder(self.root)

    def _traverse_inorder(self, node):
        if node.left:
            self._traverse_inorder(node.left)
        print(node.data, end=' ')
        if node.right:
            self._traverse_inorder(node.right)

# 创建二叉查找树
bst = BinarySearchTree()
bst.insert(10)
bst.insert(20)
bst.insert(30)
bst.insert(40)
bst.insert(50)
bst.insert(25)

# 打印二叉查找树
bst.traverse_inorder()  # 输出:10 20 25 30 40 50
进阶资源推荐

书籍推荐

虽然在本文中没有推荐特定的书籍,但对于希望进一步深入学习数据结构的人来说,以下书籍可能是一个不错的选择:

  • 《算法导论》(Introduction to Algorithms)
  • 《数据结构与算法分析》(Data Structures and Algorithm Analysis)
  • 《编程珠玑》(Programming Pearls)

在线教程和项目实践

推荐通过在线教程和项目实践来进一步巩固对数据结构的理解。以下是一些推荐的在线教程和项目实践资源:

  • 慕课网:提供丰富的数据结构课程和项目实践资源。
  • Coursera:提供由世界顶级大学和机构开设的在线课程,涵盖数据结构和算法。
  • LeetCode:提供大量的编程题目和挑战,帮助你提升编程能力和数据结构知识。
  • HackerRank:提供编程挑战和竞赛,帮助你提升编程能力和解决实际问题的能力。

通过上述资源的学习和实践,你可以进一步提高对数据结构的理解和应用能力。

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消