线性表总结

1.线性表类型

在最近的学习过程中,主要通过顺序表、链表、静态链表三个大块来进行线性表的学习,线性表是一种数据结构,一个线性表是n个具有相同特性的数据元素的有限序列。线性表的实际存储方式分为两种实现模型:顺序表、链表。

2.顺序表

2.1.顺序表定义

  • 顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数据的增删查改。

  • 顺序表一般为可动态增长的数组,要求数据是连续存储的。

2.2.顺序表设计

其主要功能如下:

  • sequentialListInit():顺序表初始化。
  • sequentialListInsert():插入数据。
  • sequentialListDelete():删除数据。
  • locateElement():已知数据求位置。
  • getElement():已知位置求数据。
  • outputList():打印顺序表数据。
  • outputMemory():打印顺序表地址。

 代码如下:

//
// Created by Caka on 2023/3/25.
//
#include <malloc.h>
#include <iostream>
 
using namespace std;
 
const int LIST_MAX_LENGTH = 10;
 
/**
 * SequentialList data structure definition
 */
typedef struct SequentialList {
    int actualLength;
 
    int data[LIST_MAX_LENGTH]; //The maximum length is fixed.
} *SequentialListPtr;
 
/**
 * Initialize a sequential list. No error checking for this function.
 */
SequentialListPtr sequentialListInit(int paraData[], int paraLength) {
    SequentialListPtr resultPtr = (SequentialListPtr) malloc(sizeof(struct SequentialList));
    for (int i = 0; i < paraLength; i++) {
        resultPtr->data[i] = paraData[i];
    }// Of for i
    resultPtr->actualLength = paraLength;
 
    return resultPtr;
}//Of sequentialListInit
 
/**
 * Insert an element into a sequential list.
 */
void sequentialListInsert(SequentialListPtr paraListPtr, int paraPosition, int paraValue) {
    // Step 1. Space check.
    if (paraListPtr->actualLength >= LIST_MAX_LENGTH) {
        cout << "Cannot insert element: because the list is full." << endl;
        return;
    }//Of if
 
    // Step 2. Position check.
    if (paraPosition < 0) {
        cout << "Cannot insert element: negative position unsupported." << endl;
        return;
    }//Of if
    if (paraPosition > paraListPtr->actualLength) {
        cout << "Cannot insert element: the position " << paraPosition << " is bigger than the list length "
             << paraListPtr->actualLength << endl;
        return;
    }//Of if
 
    // Step 3. Move the remaining part.
    for (int i = paraListPtr->actualLength; i > paraPosition; i--) {
        paraListPtr->data[i] = paraListPtr->data[i - 1];
    }//Of for i
 
    // Step 4. Insert.
    paraListPtr->data[paraPosition] = paraValue;
 
    // Step 5. Update the length.
    paraListPtr->actualLength++;
}// Of sequentialListInsert
 
/**
 * Delete an element from a sequential linear list.
 */
int sequentialListDelete(SequentialListPtr paraListPtr, int paraPosition) {
    // Step 1. Position check.
    if (paraListPtr->actualLength == 0) {
        cout << "Cannot delete element: because the list is empty." << endl;
        return -1;
    }
 
    // Step 2. Position check.
    if (paraPosition < 0) {
        cout << "Invalid position: " << paraPosition << endl;
        return -1;
    }//Of if
 
    if (paraPosition >= paraListPtr->actualLength) {
        cout << "Cannot delete element: the position " << paraPosition << " is beyond the list length "
             << paraListPtr->actualLength << endl;
        return -1;
    }//Of if
 
    // Step 3. Move the remaining part.
    int resultValue = paraListPtr->data[paraPosition];
    for (int i = paraPosition; i < paraListPtr->actualLength; i++) {
        paraListPtr->data[i] = paraListPtr->data[i + 1];
    }//Of for i
 
    // Step 4. Update the length.
    paraListPtr->actualLength--;
 
    // Step 5. Return the value.
    return resultValue;
}// Of sequentialListDelete
 
/**
 * Output the list.
 */
void outputList(SequentialListPtr paraList) {
    for (int i = 0; i < paraList->actualLength; i++) {
        cout << paraList->data[i] << " ";
    }// Of for i
    cout << endl;
}// Of outputList
 
/**
 * Output the memory for the list.
 */
void outputMemory(SequentialListPtr paraListPtr) {
    cout << "The address of the structure: " << paraListPtr << endl;
    cout << "The address of actualLength: " << &paraListPtr->actualLength << endl;
    cout << "The address of data: " << &paraListPtr->data << endl;
    cout << "The address of actual data: " << &paraListPtr->data[0] << endl;
    cout << "The address of second data: " << &paraListPtr->data[1] << endl;
}// Of outputMemory
 
/**
 * Locate an element in the list.
 */
int locateElement(SequentialListPtr paraListPtr, int paraValue) {
    for (int i = 0; i < paraListPtr->actualLength; i++) {
        if (paraListPtr->data[i] == paraValue) {
            return i;
        }// Of if
    }//Of for i
 
    return -1;
}// Of locateElement
 
/**
 * Get an element in the list.
 */
int getElement(SequentialListPtr paraListPtr, int paraPosition) {
    // Step 1. Position check.
    if (paraPosition < 0) {
        cout << "Invalid position: " << paraPosition << endl;
        return -1;
    }//Of if
 
    if (paraPosition >= paraListPtr->actualLength) {
        cout << "Cannot get element: the position " << paraPosition << "is beyond the list length "
             << paraListPtr->actualLength << endl;
        return -1;
    }//Of if
 
    return paraListPtr->data[paraPosition];
}// Of locateElement
 
/**
 * Clear elements in the list.
 */
void clearList(SequentialListPtr paraListPtr) {
    paraListPtr->actualLength = 0;
    cout << "This list has been cleared" << endl;
}// Of clearList
 
/**
 The Main function.
 */
int main() {
    int i;
    int ArrayList[5] = {1, 2, 3, 4, 5};
    cout << "---- sequentialListTest begins. ---- " << endl;
 
    // Initialize.
    SequentialListPtr tempList = sequentialListInit(ArrayList, 5);
    cout << "After initialization, the list is: " << endl;
    outputList(tempList);
 
    // Insert to the first.
    cout << "Now insert to the first, the list is: " << endl;
    sequentialListInsert(tempList, 0, 0);
    outputList(tempList);
 
    // Insert to the last.
    cout << "Now insert to the last, the list is: " << endl;
    sequentialListInsert(tempList, 6, 6);
    outputList(tempList);
 
    // Insert beyond the tail.
    cout << "Now insert beyond the tail." << endl;
    sequentialListInsert(tempList, 8, 7);
    cout << "The list is: " << endl;
    outputList(tempList);
 
    // Insert to position 3.
    int position = tempList->actualLength;
    for (i = 0; i < 5; i++) {
        cout << "After inserting " << (i + 7) << " " << "The list is :" << endl;
        sequentialListInsert(tempList, position, (i + 7));
        position += 1;
        if (position <= LIST_MAX_LENGTH) {
            outputList(tempList);
        }
    }//Of for i
    position = tempList->actualLength - 1;
 
    cout << endl;
    // Test the locateElement Function
    cout << "The number 1' position is: " << locateElement(tempList, 1) << endl;
 
    // Test the getElement Function
    cout << "The position 1' number is: " << getElement(tempList, 1) << endl;
 
    cout << endl;
    // Test the outMemory Function
    outputMemory(tempList);
    cout << endl;
 
    // Delete the first.
    cout << "Now delete the first, the list is: " << endl;
    if (sequentialListDelete(tempList, 0) != -1) {
        position -= 1;
    }
    outputList(tempList);
 
    // Delete to the last.
    cout << "Now delete the last, the list is: " << endl;
    if (sequentialListDelete(tempList, position) != -1) {
        position -= 1;
    }
    outputList(tempList);
 
    // Delete the second.
    cout << "Now delete the second, the list is: " << endl;
    if (sequentialListDelete(tempList, 1) != -1) {
        position -= 1;
    }
    outputList(tempList);
 
    // Delete the second.
    cout << "Now delete the 5th, the list is: " << endl;
    if (sequentialListDelete(tempList, 4) != -1) {
        position -= 1;
    }
    outputList(tempList);
 
    // Delete the second.
    cout << "Now delete the (-6)th, the list is: " << endl;
    if (sequentialListDelete(tempList, -6) != -1) {
        position -= 1;
    }
 
    for (i = 0; i <= 6; i++) {
        cout << "After deleting " << "the first number" << " The list is :" << endl;
        sequentialListDelete(tempList, 0);
        if (position <= LIST_MAX_LENGTH) {
            outputList(tempList);
        }
    }//Of for i
 
    // Test the clearList Function
    clearList(tempList);
 
    cout << "---- sequentialListTest ends. ---- " << endl;
    return 0;
}// Of main

运行结果如下:

---- sequentialListTest begins. ----
After initialization, the list is:
1 2 3 4 5
Now insert to the first, the list is:
0 1 2 3 4 5
Now insert to the last, the list is:
0 1 2 3 4 5 6
Now insert beyond the tail.
Cannot insert element: the position 8 is bigger than the list length 7
The list is:
0 1 2 3 4 5 6
After inserting 7 The list is :
0 1 2 3 4 5 6 7
After inserting 8 The list is :
0 1 2 3 4 5 6 7 8
After inserting 9 The list is :
0 1 2 3 4 5 6 7 8 9
After inserting 10 The list is :
Cannot insert element: because the list is full.
After inserting 11 The list is :
Cannot insert element: because the list is full.
 
The number 1' position is: 1
The position 1' number is: 1
 
The address of the structure: 0xe61650
The address of actualLength: 0xe61650
The address of data: 0xe61654
The address of actual data: 0xe61654
The address of second data: 0xe61658
 
Now delete the first, the list is:
1 2 3 4 5 6 7 8 9
Now delete the last, the list is:
1 2 3 4 5 6 7 8
Now delete the second, the list is:
1 3 4 5 6 7 8
Now delete the 5th, the list is:
1 3 4 5 7 8
Now delete the (-6)th, the list is:
Invalid position: -6
After deleting the first number The list is :
3 4 5 7 8
After deleting the first number The list is :
4 5 7 8
After deleting the first number The list is :
5 7 8
After deleting the first number The list is :
7 8
After deleting the first number The list is :
8
After deleting the first number The list is :
After deleting the first number The list is :
Cannot delete element: because the list is empty.
This list has been cleared
---- sequentialListTest ends. ----

3.链表

3.1.单链表的定义 

单链表是线性表的链式存取结构,用一组地址任意的存储单元存放线性表中的数据元素。链表中的数据是以结点来表示的,每个结点的构成:元素(数据元素的映象) + 指针(指示后继元素存储位置),元素就是存储数据的存储单元指针就是连接每个结点的地址数据

3.2.单链表与顺序表区别 

单链表与顺序表区别:这是两种不同的存储结构,顺序表是顺序存储结构,它的特点是逻辑关系上相邻的两个元素在物理位置上也相邻。但链表是链式存储结构的特点是不需要逻辑上相邻的元素在物理位置上也相邻。因为链式存储结构可以通过结点中的指针域直接找到下一个结点的位置。

优缺点:

单链表的优缺点:
1.优点:可以按照实际所需创建结点增减链表的长度,更大程度地使用内存。
2.缺点:进行尾部或者任意位置上插入或删除时时间复杂度和空间复杂度较大,每次都需要通过指针的移动找到所需要的位置,相对于顺序表查找而言效率较低。

顺序表的优缺点:
1.优点:可以通过下标直接访问所需要的数据
2.缺点:不能按实际所需分配内存,只能使用malloc或者realloc函数进行扩容,容易实现频繁扩容,容易导致内存浪费与数据泄露等问题。

3.3.单链表设计

其主要功能如下:

  • initLinkList():初始化链表。
  • printList(NodePtr paraHeader):打印链表。
  • appendElement(NodePtr paraHeader, char paraChar):添加元素。
  • insertElement(NodePtr paraHeader, char paraChar, int paraPosition):指定位置添加。
  • deleteElement(NodePtr paraHeader, char paraChar):删除元素。
  • 新增outMemory(NodePtr paraHeader):打印地址。 

代码如下:

//
// Created by A on 2023/3/28.
//
#include <iostream>
 
using namespace std;
 
/**
 * Linked list of characters. The key is data.
 */
typedef struct LinkNode {
    char data;
    struct LinkNode *next;
} LNode, *LinkList, *NodePtr;
 
/**
 * Initialize the list with a header.
 * @return The pointer to the header.
 */
LinkList initLinkList() {
    NodePtr tempHeader = (NodePtr) malloc(sizeof(LNode));
    tempHeader->data = '\0';
    tempHeader->next = NULL;
    return tempHeader;
}// Of initLinkList
 
/**
 * Print the list.
 * @param paraHeader The header of the list.
 */
void printList(NodePtr paraHeader) {
    NodePtr p = paraHeader->next;
    while (p != NULL) {
        cout << p->data;
        p = p->next;
    }// Of while
    cout << endl;
}// Of printList
 
/**
 * Add an element to the tail.
 * @param paraHeader The header of the list.
 * @param paraChar The given char.
 */
void appendElement(NodePtr paraHeader, char paraChar) {
    NodePtr p, q;
 
    // Step 1. Construct a new node.
    q = (NodePtr) malloc(sizeof(LNode));
    q->data = paraChar;
    q->next = NULL;
 
    // Step 2. Search to the tail.
    p = paraHeader;
    while (p->next != NULL) {
        p = p->next;
    }// Of while
 
    // Step 3. Now add/link.
    p->next = q;
}// Of appendElement
 
/**
 * Insert an element to the given position.
 * @param paraHeader The header of the list.
 * @param paraChar The given char.
 * @param paraPosition The given position.
 */
void insertElement(NodePtr paraHeader, char paraChar, int paraPosition) {
    NodePtr p, q;
 
    // Step 1. Search to the position.
    p = paraHeader;
    for (int i = 0; i < paraPosition; i++) {
        p = p->next;
        if (p == NULL) {
            cout << "The position " << paraPosition << " is beyond the scope of the list.";
            return;
        }// Of if
    } // Of for i
 
    // Step 2. Construct a new node.
    q = (NodePtr) malloc(sizeof(LNode));
    q->data = paraChar;
 
    // Step 3. Now link.
    cout << "linking " << paraChar << " in position " << paraPosition << endl;
    q->next = p->next;
    p->next = q;
}// Of insertElement
 
/**
 * Delete an element from the list.
 * @param paraHeader The header of the list.
 * @param paraChar The given char.
 */
void deleteElement(NodePtr paraHeader, char paraChar) {
    NodePtr p, q;
    p = paraHeader;
    while ((p->next != NULL) && (p->next->data != paraChar)) {
        p = p->next;
    }// Of while
 
    if (p->next == NULL) {
        cout << "Cannot delete " << paraChar << endl;
        return;
    }// Of if
 
    q = p->next;
    p->next = p->next->next;
    free(q);
}// Of deleteElement
 
/**
 * Output the memory for the list.
 */
void outMemory(NodePtr paraHeader) {
    NodePtr p = paraHeader->next;
    while (p != NULL) {
        cout << "The value is " << p->data << ",the address is " << p << endl;
        p = p->next;
    }// Of while
    cout << endl;
}// Of basicAddressTest
 
/**
 * The entrance.
 */
int main() {
    cout << "---- LinkListTest begins. ---- " << endl;
    // Step 1. Initialize an empty list.
    LinkList tempList = initLinkList();
    printList(tempList);
 
    // Step 2. Add some characters.
    appendElement(tempList, 'H');
    appendElement(tempList, 'e');
    appendElement(tempList, 'l');
    appendElement(tempList, 'l');
    appendElement(tempList, 'o');
    appendElement(tempList, '!');
    printList(tempList);
 
    // Step 3. Delete some characters (the first occurrence).
    deleteElement(tempList, 'e');
    deleteElement(tempList, 'a');
    deleteElement(tempList, 'o');
    printList(tempList);
 
    // Step 4. Insert to a given position.
    insertElement(tempList, 'o', 1);
    printList(tempList);
 
    outMemory(tempList);
 
    cout << "---- LinkListTest ends. ---- " << endl;
}// Of main

运行结果如下:

---- LinkListTest begins. ----
 
Hello!
Cannot delete a
Hll!
linking o in position 1
Holl!
The value is H,the address is 0x7d1670
The value is o,the address is 0x7d16f0
The value is l,the address is 0x7d16b0
The value is l,the address is 0x7d16d0
The value is !,the address is 0x7d1710
 
---- LinkListTest ends. ----

4.静态链表

4.1.静态链表的定义 

静态链表是一种比较高级的线性存储结构,融合顺序表和链表的优点,既能快速访问元素,又能快速增加或删除数据元素。静态链表存储数据,数据全部存储在数组中(此处类似顺序表),但存储位置是随机的,数据之间"的逻辑关系通过一个整形变量——next进行维持(此处理类似链表)。 

4.2.静态链表设计

功能如下:

  • initLinkedList() :初始化静态链表。
  • printList():打印静态链表数据。
  • insertElement():向静态链表指定位置插入数据。
  • deleteElement():删除静态链表中的数据。
  • 新增outputMemory():打印链表结点的数据和地址。

代码如下:

//
// Created by Caka on 2023/4/5.
//
#include <iostream>
 
using namespace std;
 
const int DEFAULT_SIZE = 6;
 
typedef struct StaticLinkedNode {
    char data;
    int next;
} *NodePtr;
 
typedef struct StaticLinkedList {
    NodePtr nodes;
    int *used;
} *ListPtr;
 
/**
 * Initialize the list with a header.
 * @return The pointer to the header.
 */
ListPtr initLinkedList() {
    // The pointer to the whole list space.
    ListPtr tempPtr = (ListPtr) malloc(sizeof(struct StaticLinkedList));
 
    // Allocate total space.
    tempPtr->nodes = (NodePtr) malloc(sizeof(struct StaticLinkedNode) * DEFAULT_SIZE);
    tempPtr->used = (int *) malloc(sizeof(int) * DEFAULT_SIZE);
 
    // The first node is the header.
    tempPtr->nodes[0].data = '\0';
    tempPtr->nodes[0].next = -1;
 
    // Only the first node is used.
    tempPtr->used[0] = 1;
    for (int i = 1; i < DEFAULT_SIZE; i++) {
        tempPtr->used[i] = 0;
    }// Of for i
 
    return tempPtr;
}// Of initLinkedList
 
/**
 * Print the list.
 * @param paraListPtr The pointer to the list.
 */
void printList(ListPtr paraListPtr) {
    int p = 0;
    while (p != -1) {
        cout << paraListPtr->nodes[p].data;
        p = paraListPtr->nodes[p].next;
    }// Of while
    cout << endl;
}// Of printList
 
/**
 * Print the address of the list.
 * @param paraListPtr The pointer to the list.
 */
void outputMemory(ListPtr paraListPtr) {
    int p = 0;
    while (p != -1) {
        cout << "The position " << p << "'node'value is " << paraListPtr->nodes[p].data << ",the position " << p
             << "'node'address is " << &paraListPtr->nodes[p] << endl;
        p = paraListPtr->nodes[p].next;
    }// Of while
    cout << endl;
}// Of printList
 
/**
 * Insert an element to the given position.
 * @param paraListPtr The position of the list.
 * @param paraChar The given char.
 * @param paraPosition The given position.
 */
void insertElement(ListPtr paraListPtr, char paraChar, int paraPosition) {
    int p, q, i;
 
    // Step 1. Search to the position.
    p = 0;
    for (i = 0; i < paraPosition; i++) {
        p = paraListPtr->nodes[p].next;
        if (p == -1) {
            cout << "The position " << paraPosition << "is beyond the scope of the list." << endl;
            return;
        }// Of if
    } // Of for i
 
    // Step 2. Construct a new node.
    for (i = 1; i < DEFAULT_SIZE; i++) {
        if (paraListPtr->used[i] == 0) {
            // This is identical to malloc.
            cout << "Space at " << i << " allocated..." << endl;
            paraListPtr->used[i] = 1;
            q = i;
            break;
        }// Of if
    }// Of for i
    if (i == DEFAULT_SIZE) {
        cout << "No space." << endl;
        return;
    }// Of if
 
    paraListPtr->nodes[q].data = paraChar;
 
    // Step 3. Now link.
    cout << "linking " << paraChar << "..." << endl;
    paraListPtr->nodes[q].next = paraListPtr->nodes[p].next;
    paraListPtr->nodes[p].next = q;
}// Of insertElement
 
/**
 * Delete an element from the list.
 * @param paraHeader The header of the list.
 * @param paraChar The given char.
 */
void deleteElement(ListPtr paraListPtr, char paraChar) {
    int p, q;
    p = 0;
    while ((paraListPtr->nodes[p].next != -1) && (paraListPtr->nodes[paraListPtr->nodes[p].next].data != paraChar)) {
        p = paraListPtr->nodes[p].next;
    }// Of while
 
    if (paraListPtr->nodes[p].next == -1) {
        cout << "Cannot delete " << paraChar << endl;
        return;
    }// Of if
 
    q = paraListPtr->nodes[p].next;
    paraListPtr->nodes[p].next = paraListPtr->nodes[paraListPtr->nodes[p].next].next;
 
    // This statement is identical to free(q)
    paraListPtr->used[q] = 0;
}// Of deleteElement
 
/**
 * The entrance.
 */
int main() {
    cout << "---- StaticLinkedListTest begins. ---- " << endl;
 
    // Step 1. Initialize an empty list.
    ListPtr tempList = initLinkedList();
    printList(tempList);
    outputMemory(tempList);
 
    // Step 2. Add some characters.
    insertElement(tempList, 'H', 0);
    insertElement(tempList, 'e', 1);
    insertElement(tempList, 'l', 2);
    insertElement(tempList, 'l', 3);
    insertElement(tempList, 'o', 4);
    insertElement(tempList, '!', 5);
    printList(tempList);
    outputMemory(tempList);
 
    // Step 3. Delete some characters (the first occurrence).
    cout << "Deleting 'e'." << endl;
    deleteElement(tempList, 'e');
    cout << "Deleting 'a'." << endl;
    deleteElement(tempList, 'a');
    cout << "Deleting 'o'." << endl;
    deleteElement(tempList, 'o');
    printList(tempList);
    outputMemory(tempList);
 
    insertElement(tempList, 'x', 1);
    printList(tempList);
    outputMemory(tempList);
 
    cout << "---- StaticLinkedListTest begins. ---- " << endl;
    return 0;
}// Of main

运行结果如下:

---- StaticLinkedListTest begins. ----
 
The position 0'node'value is  ,the position 0'node'address is 0x6a1670
 
Space at 1 allocated...
linking H...
Space at 2 allocated...
linking e...
Space at 3 allocated...
linking l...
Space at 4 allocated...
linking l...
Space at 5 allocated...
linking o...
No space.
 Hello
The position 0'node'value is  ,the position 0'node'address is 0x6a1670
The position 1'node'value is H,the position 1'node'address is 0x6a1678
The position 2'node'value is e,the position 2'node'address is 0x6a1680
The position 3'node'value is l,the position 3'node'address is 0x6a1688
The position 4'node'value is l,the position 4'node'address is 0x6a1690
The position 5'node'value is o,the position 5'node'address is 0x6a1698
 
Deleting 'e'.
Deleting 'a'.
Cannot delete a
Deleting 'o'.
 Hll
The position 0'node'value is  ,the position 0'node'address is 0x6a1670
The position 1'node'value is H,the position 1'node'address is 0x6a1678
The position 3'node'value is l,the position 3'node'address is 0x6a1688
The position 4'node'value is l,the position 4'node'address is 0x6a1690
 
Space at 2 allocated...
linking x...
 Hxll
The position 0'node'value is  ,the position 0'node'address is 0x6a1670
The position 1'node'value is H,the position 1'node'address is 0x6a1678
The position 2'node'value is x,the position 2'node'address is 0x6a1680
The position 3'node'value is l,the position 3'node'address is 0x6a1688
The position 4'node'value is l,the position 4'node'address is 0x6a1690
 
---- StaticLinkedListTest begins. ----

 

 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值