《Java数据结构和算法》第二版 Robert lafore 编程作业 第十章

《Java数据结构和算法》第二版 Robert lafore  编程作业 第十章

转自:http://blog.csdn.net/zhch152/article/details/7966744

/*
 	编程作业  			
 	10.1 这个作业很容易。编写一个方法能返回2-3-4树中的最小值.
	10.2 编写方法中序遍历2-3-4树。它能有序地显示所有的数据项。
	10.3 2-3-4树可以用于排序。编写sort()方法,从main()方法中传入关键字的数组
		 并在排序后把它们写回数组中去。
	10.4 修改tree234.java程序(清单10.1),使它可以创建并操作2-3树。要求显示
		 树并可以查找。还要能够插入数据项,但是只限在叶节点(要分裂的)的父节
		 点不需要再分裂的情况。这就是说split()方法不需要递归。编写insert()方
		 法,记得在找到要插入合适的叶节点之前不需要节点分裂。找到后,如果叶节
		 点满了就要分裂。也需要能够分裂根,但只限它是叶节点的情况下。根据这个
		 限制,只能插入少于9个数据项,否则程序就会起冲突了。
 	10.5 扩展编程作业10.4的程序,使split()方法是递归的并可以处理一个满的子节
 		 点的满父节点的情况。这就允许不限个数地插入节点。注意在递归的split()
 		 例程中,在决定数据项要转向何处和子节点要连接到哪个里之前要先分裂父节
 		 点。
 */
package chap10;
 
// tree234.java
// demonstrates 234 tree
// to run this program: C>java Tree234App
import java.io.*;
 

class DataItem {
	public long dData;          // one data item
 
	// --------------------------------------------------------------
 
	public DataItem(long dd)    // constructor
	{
		dData = dd;
	}
 
	// --------------------------------------------------------------
	public void displayItem()   // display item, format "/27"
	{
		System.out.print("/" + dData);
	}
	// --------------------------------------------------------------
}  // end class DataItem
// //
 
class Node {
	private static final int ORDER = 4;
	private int numItems;
	private Node parent;
	private Node childArray[] = new Node[ORDER];
	private DataItem itemArray[] = new DataItem[ORDER - 1];
 
	// -------------------------------------------------------------
	// connect child to this node
	public void connectChild(int childNum, Node child) {
		childArray[childNum] = child;
		if (child != null)
			child.parent = this;
	}
 
	// -------------------------------------------------------------
	// disconnect child from this node, return it
	public Node disconnectChild(int childNum) {
		Node tempNode = childArray[childNum];
		childArray[childNum] = null;
		return tempNode;
	}
 
	// -------------------------------------------------------------
	public Node getChild(int childNum) {
		return childArray[childNum];
	}
 
	// -------------------------------------------------------------
	public Node getParent() {
		return parent;
	}
 
	// -------------------------------------------------------------
	public boolean isLeaf() {
		return (childArray[0] == null) ? true : false;
	}
 
	// -------------------------------------------------------------
	public int getNumItems() {
		return numItems;
	}
 
	// -------------------------------------------------------------
	public DataItem getItem(int index)   // get DataItem at index
	{
		return itemArray[index];
	}
 
	// -------------------------------------------------------------
	public boolean isFull() {
		return (numItems == ORDER - 1) ? true : false;
	}
 
	// -------------------------------------------------------------
	public int findItem(long key)       // return index of
	{                                    // item (within node)
		for (int j = 0; j < ORDER - 1; j++)         // if found,
		{                                 // otherwise,
			if (itemArray[j] == null)          // return -1
				break;
			else if (itemArray[j].dData == key)
				return j;
		}
		return -1;
	}  // end findItem
		// -------------------------------------------------------------
 
	public int insertItem(DataItem newItem) {
		// assumes node is not full
		numItems++;                          // will add new item
		long newKey = newItem.dData;         // key of new item
 
		for (int j = ORDER - 2; j >= 0; j--)        // start on right,
		{                                 // examine items
			if (itemArray[j] == null)          // if item null,
				continue;                      // go left one cell
			else                              // not null,
			{                              // get its key
				long itsKey = itemArray[j].dData;
				if (newKey < itsKey)            // if it's bigger
					itemArray[j + 1] = itemArray[j]; // shift it right
				else {
					itemArray[j + 1] = newItem;   // insert new item
					return j + 1;                 // return index to
				}                           // new item
			}  // end else (not null)
		}  // end for // shifted all items,
		itemArray[0] = newItem;              // insert new item
		return 0;
	}  // end insertItem()
		// -------------------------------------------------------------
 
	public DataItem removeItem()        // remove largest item
	{
		// assumes node not empty
		DataItem temp = itemArray[numItems - 1];  // save item
		itemArray[numItems - 1] = null;           // disconnect it
		numItems--;                             // one less item
		return temp;                            // return item
	}
 
	// -------------------------------------------------------------
	public void displayNode()           // format "/24/56/74/"
	{
		for (int j = 0; j < numItems; j++)
			itemArray[j].displayItem();   // "/56"
		System.out.println("/");         // final "/"
	}
	// -------------------------------------------------------------
}  // end class Node
// //
 
class Tree234 {
	private Node root = new Node();            // make root node
 
	// -------------------------------------------------------------
 
	public int find(long key) {
		Node curNode = root;
		int childNumber;
		while (true) {
			if ((childNumber = curNode.findItem(key)) != -1)
				return childNumber;               // found it
			else if (curNode.isLeaf())
				return -1;                        // can't find it
			else
				// search deeper
				curNode = getNextChild(curNode, key);
		}  // end while
	}
 
	// -------------------------------------------------------------
	// insert a DataItem
	public void insert(long dValue) {
		Node curNode = root;
		DataItem tempItem = new DataItem(dValue);
 
		while (true) {
			if (curNode.isFull())               // if node full,
			{
				split(curNode);                   // split it
				curNode = curNode.getParent();    // back up
												// search once
				curNode = getNextChild(curNode, dValue);
			}  // end if(node is full)
 
			else if (curNode.isLeaf())          // if node is leaf,
				break;                            // go insert
			// node is not full, not a leaf; so go to lower level
			else
				curNode = getNextChild(curNode, dValue);
		}  // end while
 
		curNode.insertItem(tempItem);       // insert new DataItem
	}  // end insert()
		// -------------------------------------------------------------
 
	public void split(Node thisNode)     // split the node
	{
		// assumes node is full
		DataItem itemB, itemC;
		Node parent, child2, child3;
		int itemIndex;
 
		itemC = thisNode.removeItem();    // remove items from
		itemB = thisNode.removeItem();    // this node
		child2 = thisNode.disconnectChild(2); // remove children
		child3 = thisNode.disconnectChild(3); // from this node
 
		Node newRight = new Node();       // make new node
 
		if (thisNode == root)                // if this is the root,
		{
			root = new Node();                // make new root
			parent = root;                    // root is our parent
			root.connectChild(0, thisNode);   // connect to parent
		} else
			// this node not the root
			parent = thisNode.getParent();    // get parent
 
		// deal with parent
		itemIndex = parent.insertItem(itemB); // item B to parent
		int n = parent.getNumItems();         // total items?
 
		for (int j = n - 1; j > itemIndex; j--)          // move parent's
		{                                      // connections
			Node temp = parent.disconnectChild(j); // one child
			parent.connectChild(j + 1, temp);        // to the right
		}
		// connect newRight to parent
		parent.connectChild(itemIndex + 1, newRight);
 
		// deal with newRight
		newRight.insertItem(itemC);       // item C to newRight
		newRight.connectChild(0, child2); // connect to 0 and 1
		newRight.connectChild(1, child3); // on newRight
	}  // end split()
		// -------------------------------------------------------------
		// gets appropriate child of node during search for value
 
	public Node getNextChild(Node theNode, long theValue) {
		int j;
		// assumes node is not empty, not full, not a leaf
		int numItems = theNode.getNumItems();
		for (j = 0; j < numItems; j++)          // for each item in node
		{                               // are we less?
			if (theValue < theNode.getItem(j).dData)
				return theNode.getChild(j);  // return left child
		}  // end for // we're greater, so
		return theNode.getChild(j);        // return right child
	}
 
	// -------------------------------------------------------------
	public void displayTree() {
		recDisplayTree(root, 0, 0);
	}
 
	// -------------------------------------------------------------
	private void recDisplayTree(Node thisNode, int level, int childNumber) {
		System.out.print("level=" + level + " child=" + childNumber + " ");
		thisNode.displayNode();               // display this node
 
		// call ourselves for each child of this node
		int numItems = thisNode.getNumItems();
		for (int j = 0; j < numItems + 1; j++) {
			Node nextNode = thisNode.getChild(j);
			if (nextNode != null)
				recDisplayTree(nextNode, level + 1, j);
			else
				return;
		}
	}  // end recDisplayTree()
 
	// -------------------------------------------------------------
	// ==============================================================
	// 编程作业 10.1
	// 这里没有考虑空树的情况,调用前确保树不为空
	public long getMinValue() {
		Node parent = root;
		Node current = root;
		while (current != null) {
			parent = current;
			current = current.getChild(0);
		}
		return parent.getItem(0).dData;
	}
 
	// ==============================================================
	public void traverse(int traverseType) {
		switch (traverseType) {
		case 1:
			System.out.print("\nPreorder traversal: ");
			// preOrder(root);
			break;
		case 2:
			System.out.print("\nInorder traversal:  ");
			inOrder(root);
			break;
		case 3:
			System.out.print("\nPostorder traversal: ");
			// postOrder(root);
			break;
		}
		System.out.println();
	}
 
	// ==============================================================
	// 编程作业 10.2
	private void inOrder(Node root) {
		if (root == null) {
			return;
		}
		int i = 0;
		for (; i < root.getNumItems(); i++) {
			inOrder(root.getChild(i));
			System.out.print(root.getItem(i).dData + " ");
		}
		if (i != 0) {
			inOrder(root.getChild(i));
		}
	}
 
	// ==============================================================
	// 编程作业 10.3
	public void sort(long[] array) {
		this.root = new Node();
		for (int i = 0; i < array.length; i++) {
			this.insert(array[i]);
		}
		inOrderForSort(array, root, 0);
	}
 
	// ==============================================================
 
	private int inOrderForSort(long[] array, Node root, int arrayIndex) {
		if (root == null) {
			return arrayIndex;
		}
		int i = 0;
		for (; i < root.getNumItems(); i++) {
			arrayIndex = inOrderForSort(array, root.getChild(i), arrayIndex);
			array[arrayIndex++] = root.getItem(i).dData; // arrayIndex只在这里增加
		}
		if (i != 0) {
			arrayIndex = inOrderForSort(array, root.getChild(i), arrayIndex);
		}
		return arrayIndex;
	}
	// ==============================================================
}  // end class Tree234
// //
 
public class Tree234App {
	public static void main(String[] args) throws IOException {
		long value;
		Tree234 theTree = new Tree234();
 
		theTree.insert(10);
		theTree.insert(20);
		theTree.insert(50);
		theTree.insert(40);
		theTree.insert(60);
		theTree.insert(30);
		theTree.insert(70);
		// ========================================
		// 编程作业 10.1
		System.out.println(theTree.getMinValue());
		// ========================================
		// 编程作业 10.2
		theTree.traverse(2);// 中序遍历
		// ========================================
		// 编程作业 10.3
		long[] array = { 1, 2, 3, 4, 8, 7, 6, 5 };
		theTree.sort(array);
		for (long l : array) {
			System.out.print(l + " ");
		}
		// ========================================
 
	}  // end main()
		// --------------------------------------------------------------
 
	public static String getString() throws IOException {
		InputStreamReader isr = new InputStreamReader(System.in);
		BufferedReader br = new BufferedReader(isr);
		String s = br.readLine();
		return s;
	}
 
	// --------------------------------------------------------------
	public static char getChar() throws IOException {
		String s = getString();
		return s.charAt(0);
	}
 
	// -------------------------------------------------------------
	public static int getInt() throws IOException {
		String s = getString();
		return Integer.parseInt(s);
	}
	// -------------------------------------------------------------
}  // end class Tree234App
// //
 
package chap10;
 
// tree234.java
// demonstrates 234 tree
// to run this program: C>java Tree234App
import java.io.*;
 

class Node1 {
	private static final int ORDER = 3;
	private int numItems;
	private Node1 parent;
	private Node1 childArray[] = new Node1[ORDER];
	private DataItem itemArray[] = new DataItem[ORDER - 1];
 
	// -------------------------------------------------------------
	// connect child to this node
	public void connectChild(int childNum, Node1 child) {
		childArray[childNum] = child;
		if (child != null)
			child.parent = this;
	}
 
	// -------------------------------------------------------------
	// disconnect child from this node, return it
	public Node1 disconnectChild(int childNum) {
		Node1 tempNode = childArray[childNum];
		childArray[childNum] = null;
		return tempNode;
	}
 
	// -------------------------------------------------------------
	public Node1 getChild(int childNum) {
		return childArray[childNum];
	}
 
	// -------------------------------------------------------------
	public Node1 getParent() {
		return parent;
	}
 
	// -------------------------------------------------------------
	public boolean isLeaf() {
		return (childArray[0] == null) ? true : false;
	}
 
	// -------------------------------------------------------------
	public int getNumItems() {
		return numItems;
	}
 
	// -------------------------------------------------------------
	public DataItem getItem(int index)   // get DataItem at index
	{
		return itemArray[index];
	}
 
	// -------------------------------------------------------------
	public boolean isFull() {
		return (numItems == ORDER - 1) ? true : false;
	}
 
	// -------------------------------------------------------------
	public int findItem(long key)       // return index of
	{                                    // item (within node)
		for (int j = 0; j < ORDER - 1; j++)         // if found,
		{                                 // otherwise,
			if (itemArray[j] == null)          // return -1
				break;
			else if (itemArray[j].dData == key)
				return j;
		}
		return -1;
	}  // end findItem
		// -------------------------------------------------------------
 
	public int insertItem(DataItem newItem) {
		// assumes node is not full
		numItems++;                          // will add new item
		long newKey = newItem.dData;         // key of new item
 
		for (int j = ORDER - 2; j >= 0; j--)        // start on right,
		{                                 // examine items
			if (itemArray[j] == null)          // if item null,
				continue;                      // go left one cell
			else                              // not null,
			{                              // get its key
				long itsKey = itemArray[j].dData;
				if (newKey < itsKey)            // if it's bigger
					itemArray[j + 1] = itemArray[j]; // shift it right
				else {
					itemArray[j + 1] = newItem;   // insert new item
					return j + 1;                 // return index to
				}                           // new item
			}  // end else (not null)
		}  // end for // shifted all items,
		itemArray[0] = newItem;              // insert new item
		return 0;
	}  // end insertItem()
		// -------------------------------------------------------------
 
	public DataItem removeItem()        // remove largest item
	{
		// assumes node not empty
		DataItem temp = itemArray[numItems - 1];  // save item
		itemArray[numItems - 1] = null;           // disconnect it
		numItems--;                             // one less item
		return temp;                            // return item
	}
 
	// -------------------------------------------------------------
	public void displayNode()           // format "/24/56/74/"
	{
		for (int j = 0; j < numItems; j++)
			itemArray[j].displayItem();   // "/56"
		System.out.println("/");         // final "/"
	}
	// -------------------------------------------------------------
}  // end class Node
// //
 
class Tree23 {
	private Node1 root = new Node1();            // make root node
 
	// -------------------------------------------------------------
 
	public int find(long key) {
		Node1 curNode = root;
		int childNumber;
		while (true) {
			if ((childNumber = curNode.findItem(key)) != -1)
				return childNumber;               // found it
			else if (curNode.isLeaf())
				return -1;                        // can't find it
			else
				// search deeper
				curNode = getNextChild(curNode, key);
		}  // end while
	}
 
	// -------------------------------------------------------------
	// insert a DataItem
	public void insert(long dValue) {
		Node1 curNode = root;
		DataItem tempItem = new DataItem(dValue);
 
		while (true) {
			if (curNode.isLeaf())          // if node is leaf,
				break;                            // go insert
			// node is not full, not a leaf; so go to lower level
			else
				curNode = getNextChild(curNode, dValue);
		}  // end while
		if (curNode.isFull()) {
			// split(curNode, tempItem); //只能分裂叶节点
			split1(curNode, tempItem, null); // 可以递归分裂
		} else {
			curNode.insertItem(tempItem);       // insert new DataItem
		}
	}  // end insert()
 
	// -------------------------------------------------------------
 
	// =============================================================
	// 编程作业 10.4
	// thisNode 当前要分裂的节点 ,只能为叶子这点
	// newItem 要插入的数据项
	public void split(Node1 thisNode, DataItem newItem)     // split the node
	{
		// assumes node is full
		DataItem itemB, itemC; // itemB保存中间数据项,itemC保存最大数据项
		Node1 parent, child1, child2;
		int itemIndex;
 
		// 分裂当前节点
		// 根据新数据项与当前节点中两个数据项的比较,分三种情况:
		if (thisNode.getItem(0).dData > newItem.dData) { // 1.新数据项最小
			itemC = thisNode.removeItem();
			itemB = thisNode.removeItem();
			thisNode.insertItem(newItem);
		} else if (thisNode.getItem(1).dData < newItem.dData) {// 2.新数据项最大
			itemC = newItem;
			itemB = thisNode.removeItem();
		} else {// 3.新数据项在中间
			itemC = thisNode.removeItem();
			itemB = newItem;
		}
		// 新节点
		Node1 newRight = new Node1();       // make new node
		newRight.insertItem(itemC);       // item C to newRight
 
		// 得到父节点
		if (thisNode != root) {
			parent = thisNode.getParent();
		} else {
			root = new Node1();                // make new root
			parent = root;                    // root is our parent
			root.connectChild(0, thisNode);   // connect to parent
		}
 
		// 中间数据项插入到父节点
		if (parent.isFull()) {
			System.out.println("还未实现父节点分裂!!!");
		} else {
			itemIndex = parent.insertItem(itemB);
			int n = parent.getNumItems();         // total items?
			for (int j = n - 1; j > itemIndex; j--)          // move parent's
			{                                      // connections
				Node1 temp = parent.disconnectChild(j); // one child
				parent.connectChild(j + 1, temp);        // to the right
			}
			// connect newRight to parent
			parent.connectChild(itemIndex + 1, newRight);
		}
	}  // end split()
 
	// =============================================================
	// 编程作业 10.5
	// thisNode 当前要分裂的节点
	// newItem 要插入的数据项
	// newNode 上次分裂(子节点分裂)得到的新节点,如果是第一次分裂(叶节点分裂),则为null
	public void split1(Node1 thisNode, DataItem newItem, Node1 newNode) {
		// assumes node is full
		DataItem itemB, itemC; // itemB保存中间数据项,itemC保存最大数据项
		Node1 parent, child1, child2, newRight;
		int itemIndex;
 
		// 分裂当前节点
		// 根据新数据项与当前节点中两个数据项的比较,分三种情况:
		if (thisNode.getItem(0).dData > newItem.dData) { // 1.新数据项最小
			itemC = thisNode.removeItem();
			itemB = thisNode.removeItem();
			thisNode.insertItem(newItem);
 
			child1 = thisNode.disconnectChild(1);
			child2 = thisNode.disconnectChild(2);
			thisNode.connectChild(1, newNode);
 
			newRight = new Node1();       // make new node
			newRight.insertItem(itemC);       // item C to newRight
			newRight.connectChild(0, child1);
			newRight.connectChild(1, child2);
		} else if (thisNode.getItem(1).dData < newItem.dData) { // 2.新数据项最大
			itemC = newItem;
			itemB = thisNode.removeItem();
			child2 = thisNode.disconnectChild(2);
 
			newRight = new Node1();       // make new node
			newRight.insertItem(itemC);       // item C to newRight
			newRight.connectChild(0, child2);
			newRight.connectChild(1, newNode);
		} else { // 3.新数据项在中间
			itemC = thisNode.removeItem();
			itemB = newItem;
			child2 = thisNode.disconnectChild(2);
 
			newRight = new Node1();       // make new node
			newRight.insertItem(itemC);       // item C to newRight
			newRight.connectChild(0, newNode);
			newRight.connectChild(1, child2);
		}
 
		// 得到父节点
		if (thisNode != root) { // 当前节点是非根节点
			parent = thisNode.getParent();
		} else {// 当前节点是根节点
			root = new Node1();                // make new root
			parent = root;                    // root is our parent
			root.connectChild(0, thisNode);   // connect to parent
		}
 
		// 中间数据项插入到父节点
		if (parent.isFull()) { // 父节点是满,则递归分裂父节点
			split1(parent, itemB, newRight);
		} else { // 父节点不满,直接插入
			itemIndex = parent.insertItem(itemB);
			// 调整相应的子节点
			if (itemIndex == 0) {// 插入到第一个
				parent.connectChild(2, parent.disconnectChild(1));
				parent.connectChild(1, newRight);
			} else { // 插入到第二个
				parent.connectChild(2, newRight);
			}
		}
	}  // end split()
 
	// =============================================================
	// -------------------------------------------------------------
	// gets appropriate child of node during search for value
 
	public Node1 getNextChild(Node1 theNode, long theValue) {
		int j;
		// assumes node is not empty, not full, not a leaf
		int numItems = theNode.getNumItems();
		for (j = 0; j < numItems; j++)          // for each item in node
		{                               // are we less?
			if (theValue < theNode.getItem(j).dData)
				return theNode.getChild(j);  // return left child
		}  // end for // we're greater, so
		return theNode.getChild(j);        // return right child
	}
 
	// -------------------------------------------------------------
	public void displayTree() {
		recDisplayTree(root, 0, 0);
	}
 
	// -------------------------------------------------------------
	private void recDisplayTree(Node1 thisNode, int level, int childNumber) {
		System.out.print("level=" + level + " child=" + childNumber + " ");
		thisNode.displayNode();               // display this node
 
		// call ourselves for each child of this node
		int numItems = thisNode.getNumItems();
		for (int j = 0; j < numItems + 1; j++) {
			Node1 nextNode = thisNode.getChild(j);
			if (nextNode != null)
				recDisplayTree(nextNode, level + 1, j);
			else
				return;
		}
	}  // end recDisplayTree()
		// -------------------------------------------------------------\
}  // end class Tree234
// //
 
public class Tree23App {
	public static void main(String[] args) throws IOException {
		long value;
		Tree23 theTree = new Tree23();
 
		// ==================================================
		// 编程作业 10.4
		// theTree.insert(50);
		// theTree.insert(40);
		// theTree.insert(60);
		// theTree.insert(30);
		// theTree.insert(70);
		// theTree.insert(80);
		// theTree.insert(90);
		// theTree.insert(65);
		// // theTree.insert(20); //只能插入少于9个数据项
 
		// ==================================================
		// 编程作业 10.5
		theTree.insert(50);
		theTree.insert(40);
		theTree.insert(60);
		theTree.insert(30);
		theTree.insert(70);
		theTree.insert(80);
		theTree.insert(90);
		theTree.insert(65);
		theTree.insert(100);
		theTree.insert(85);
		theTree.insert(95);
		theTree.insert(86);
		// ==================================================
 
		while (true) {
			System.out.print("Enter first letter of ");
			System.out.print("show, insert, or find: ");
			char choice = getChar();
			switch (choice) {
			case 's':
				theTree.displayTree();
				break;
			case 'i':
				System.out.print("Enter value to insert: ");
				value = getInt();
				theTree.insert(value);
				break;
			case 'f':
				System.out.print("Enter value to find: ");
				value = getInt();
				int found = theTree.find(value);
				if (found != -1)
					System.out.println("Found " + value);
				else
					System.out.println("Could not find " + value);
				break;
			default:
				System.out.print("Invalid entry\n");
			}  // end switch
		}  // end while
	}  // end main()
		// --------------------------------------------------------------
 
	public static String getString() throws IOException {
		InputStreamReader isr = new InputStreamReader(System.in);
		BufferedReader br = new BufferedReader(isr);
		String s = br.readLine();
		return s;
	}
 
	// --------------------------------------------------------------
	public static char getChar() throws IOException {
		String s = getString();
		return s.charAt(0);
	}
 
	// -------------------------------------------------------------
	public static int getInt() throws IOException {
		String s = getString();
		return Integer.parseInt(s);
	}
	// -------------------------------------------------------------
}  // end class Tree234App
// //
 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值