课本上面的一些_板子

课本上的板子
(真的全都不会,怎么办
哎 又要考试辽

一、线性表 栈 队列
1.线性表
线性表是逻辑层面上的线性,是元素的有限序列
(1)顺序表实现

template <class E>
class Alist{
 private:
  int maxSize;
  int listSize;
  int fence;
  E* listArray;
 public:
  Alist(int size=5000){
   maxSize=size;
   listSize=fence=0;
   listArray = new E[maxSize];
  }
  ~Alist(){
   delete [] listArray;
  }
  void clear(){
   delete [] listArray;
   listSize= fence = 0;
   listArray = new E[maxSize];
  }
  bool insert(const E& it){
   listSize++;
   for(int i=fence+1;i<=listSize;i++){
    listArray[i]=listArray[i-1];
   }
   listArray[fence]=it; 
  }
  bool append(const E& it){
   listArray[++listSize]=it;
  }
  bool remove(E&){
   for(int i=fence;i<listSize;i++){
    listArray[i]=listArray[i+1];
   }
   delete listArray[listSize--];
  }
  void setStart(){
   fence = 0;
  };
  void setEnd(){
   fence = listSize;
  };
  void prev(){
   fence = fence-1>=0?fence-1:0;
  };
  void next(){
   fence = fence+1<=listSize?fence+1:listSize; 
  };
  int leftLength(){
   return fence;
  }
  int rightLength(){
   return listSize-fence;
  }
  bool setPos(int pos){
   if(pos>=0 && pos<=listSize) fence=pos;
   return pos>=0 && pos<=listSize;
  }
  bool getValue(E &it) const{
   it = listArray[fence];
   return true; 
  }
  void print() const{
   int tmp=0;
   while(tmp<fence) cout<<listArray[tmp++]<<" ";
   cout<<" | ";
   while(tmp<listSize) cout<<listArray[tmp++]<<" ";
   cout<<endl; 
  }
}; 

(2)链表

template <typename E>
class Link{
 public:
  E ele;
  Link* nxt;
  Link(const E& e,Link* nxtval=NULL){
   ele = e;
   nxt=nxtval;
  }
  Link(Link* nxtval=NULL){
   nxt=nxtval;
  }
};
template <typename E> 
class Llist{
private:
 Link<E>* head;
 Link<E>* tail;
 Link<E>* fence;
 int leftcnt;
 int rightcnt;
 void ini(){
  fence = tail = head = new Link<E>;
  leftcnt=rightcnt=0;
 } 
 void removeall(){
  while(head!=NULL){
   fence=head;
   head=head->nxt;
   delete fence;
  }
 }
 public:
  Llist(){
   ini();
  };
  ~Llist(){
   removeall();
  }
  void clear(){
   removeall();
   ini();
  }
  bool insert(const E& it){
   fence->nxt = new Link<E>(it,fence->nxt);
  }
  bool append(const E& it){
   tail->nxt = new Link<E>(it);
  }
  bool remove(const E& it){
   if(fence->nxt==NULL) return 0;
   it = fence->nxt->ele;
   Link<E>* tmp=fence->nxt;
   fence->nxt=tmp->nxt;
   //details
   if(tail==tmp) tail=fence;
   delete tmp;
   //details
   rightcnt--;
   return 1;
  }
  void serStart(){
   fence = head;
   rightcnt += leftcnt;
   leftcnt=0;
  }
  void setEnd(){
   fence = tail;
   leftcnt+=rightcnt;
   rightcnt=0;
  }
  void prev(){
   if(fence==head) return;
   Link<E>*tmp=head;
   while(tmp->nxt!=fence){
    tmp=tmp->nxt;
   }
   fence=tmp;
   leftcnt--;
   rightcnt++;
  }
  void next(){
   if(fence!=tail){
    fence=fence->nxt;
    rightcnt--;
    leftcnt++;
   }
  }
  int leftLength(){
   return leftcnt;
  }
  int rightLength(){
   return rightcnt;
  }
  bool setPos(int pos){
   if(pos<0||pos>rightcnt+leftcnt){
    return 0;
   }
   fence=head;
   for(int i=0;i<pos;i++){
    fence=fence->nxt;
   }
   return 1;
  }
  bool getValue(E& it){
   if(rightcnt==0) return 0;
   it = fence->nxt->ele;
   return 1;
  }
  void print(){
   Link<E>* tmp=head;
   while(tmp!=tail){
    cout<<tmp->ele<<" ";
    tmp=tmp->nxt;
   }
   cout<<tail->nxt<<endl;
  }
}; 

2.栈
先进后出,这种短短的代码就比较令人愉悦
(1)顺式栈

//顺式栈
template<typename E>
class AStack{
 private:
  int size;
  int top;
  E* listarray;
 public:
  AStack(int s=10){
   size=s;
   top=0;
   listarray = new E[size];
  }
  ~AStack(){
   delete []listarray;
  }
  void clear(){
   top=0;
  }
  bool push(const E&it){
   if(top==size) return 0;
   listarray[size++]=it;
   return 1;
  }
  bool pop(E& it){
   if(top==0) return 0;
   it =listarray[--top];
   return 1;
  }
  bool topValue(E& it) const{
   if(top==0) return 0;
   it=listarray[top-1];
   return 1;
  }
  int length()const{
   return top;
  }
};

(2)链式栈

//链式栈 
template<typename E>
class LStack{
 private:
  int size;
  Link<E>* top;
 public:
  LStack(int s=10){
   size=s;
   top=NULL;
  };
  ~LStack(){
   clear();
  };
  void clear(){
   while(top!=NULL){
    Link<E>* tmp=top;
    top=top->nxt;
    size=0;
    delete top;   
   }   
  }
  bool push(const E& it){
   size++;
   top = new Link<E>(it,top);
   return 1;
  }
  bool pop(E& it){
   if(size==0) return 0;
   Link<E>* tmp=top->nxt;
   delete top;
   top=tmp;
   size--;
   return 1;
  }
  bool topValue(E& it){
   if(size==0) return 0;
   it = top->ele;
   return 1;
  }
  int length() const{
   return size;
  }
  
};

3.队列
先进先出
昨天堕落完躺在床上又开始忏悔了呢(所以这就是我看漫画的理由吗
(不是)
线性实现
取模完了计算个数 下标的时候都要注意

//队列 
template<typename E>
class AQueue{
 private:
  int size;
  int front;
  int rear;
  E* listarray;
 public:
  AQueue(int s=100){
   size=s+1;
   rear=0;
   front=1;
   listarray = new E[size];
  } 
  ~AQueue(){
   delete []listarray;
  }
  void clear(){
   front=rear;
  }
  bool enqueue(const E& it){
   //如果装不下新的元素
   if((rear+2)%size==front) return 0;
   rear=(rear+1)%size;
   listarray[rear]=it;
   return 1; 
  }
  bool dequeue(E& it){
   if(length()==0) return 0;
   it = listarray[front];
   front=(front+1)%size;
   return 1;
  }
  bool frontValue(E& it){
   if(length()==0) return 0;
   it = listarray[front];
   return 1;
  }
  virtual int length()const{
   return ((rear+size)-front+1)%size;
  }
};

链表实现

//链式队列
template<typename E>
class LQueue{
 private:
  Link<E>* front;
  Link<E>* rear;
  int size;
 public:
  LQueue(int s=1000){
   size=s+1;
   front=rear=NULL;
  }
  ~LQueue(){
   clear();
  }
  void clear(){
   while(front!=NULL){
    Link<E>* tmp=front;
    front=front->nxt;
    delete tmp;
   }
   rear=NULL;
   size=0;
  }
  bool enqueue(const E& it){
   if(rear==NULL)
    front = rear = new Link<E>(it,NULL);
   else{
    rear->nxt = new Link<E>(it,NULL);
    rear=rear->nxt;
   }
   size++;
   return 1;
  }
  bool dequeue(E& it){
   if(length()==0) return 0;
   Link<E>* tmp=front;
   it = front->ele;
   front = front->nxt;
   delete tmp;
   if(front=NULL) rear=NULL;
   size--;
   return 1;
  }
  bool frontValue(E& it){
   if(length()==0) return 0;
   it=front->ele;
   return 1; 
  }
  virtual int length()const{
   return size;
  }
}; 

二、内排序
1.O(n^2)插入、冒泡、选择

int b[30];
int n=10;
int a[30]={0,4,2,1,94,2,343,54,233,5};
void print(int c[]){
 for(int i=1;i<=n;i++){
  cout<<c[i]<<" ";
 }
 cout<<endl;
}
void ini(){
 for(int i=1;i<=n;i++){
  b[i]=a[i];
 }
 }

插入:两层for循环,内层与相邻元素比较

void InsertionSort(){
 ini();
 for(int i=1;i<=n;i++){
  for(int j=i;j>=0;j--){
   //!
   if(b[j]<b[j-1]){
    swap(b[j-1],b[j]);
   }
  }
 }
 print(b);
}

冒泡:两层for循环,内层与外层比较

void BubbleSort(){
 ini();
 for(int i=1;i<=n;i++){
  for(int j=i;j<=n;j++){
   if(b[i]>b[j]){
    swap(b[i],b[j]);
   }
  }
 }
 print(b); 
}

选择排序:按序选择,不断更新

void InsertionSort(){
 ini();
 for(int i=1;i<=n;i++){
  for(int j=i;j>=0;j--){
   //!
   if(b[j]<b[j-1]){
    swap(b[j-1],b[j]);
   }
  }
 }
 print(b);
}

2.O(n^1.5)希尔
这个真的挺秀的,优化的力量

void inssort2(int b[],int n,int incr){
 for(int i=incr;i<=n;i+=incr){
  for(int j=i;j>incr;j-=incr){
   if(b[j]<b[j-incr])
    swap(b[j],b[j-incr]);
  }
 }
}
void ShellSort(){
 ini();
 for(int i=n/2;i>1;i/=2){
  for(int j=1;j<=i;j++){
   inssort2(&b[j],n-j,i);
  }
 }
 inssort2(b,n,1);
 print(b);
}

3.O(nlogn)归并、快排

归并也有个优化,是在数据范围小的时候用插入排序
以后序号还是从0开始吧,不然老是会出现神秘的bug

void inssort2(int b[],int n,int incr){
 for(int i=incr;i<=n;i+=incr){
  for(int j=i;j>incr;j-=incr){
   if(b[j]<b[j-incr])
    swap(b[j],b[j-incr]);
  }
 }
}
//Merge
int tmp[5000];
void MergeSort(int b[],int tmp[],int l,int r){
 if(l==r) return;
 int mid=(l+r)>>1;
 MergeSort(b,tmp,l,mid);
 MergeSort(b,tmp,mid+1,r);
 for(int i=l;i<=r;i++){
  tmp[i]=b[i];
 }
 int i=l,j=mid+1;
 for(int cur=l;cur<=r;cur++){
  //左边到头了 
  if(i==mid+1){
   b[cur]=tmp[j++];
  }
  //右边用完了 
  //注意这里是大于,大于! 
  else if(j>r){
   b[cur]=tmp[i++];
  }
  else if(tmp[i]>tmp[j]){
   b[cur]=tmp[j++];
  }
  else b[cur]=tmp[i++];
 }
}
//MergeSort优化版 
const int THRESHOLD = 1;
void MergeSort2(int b[],int tmp[],int l,int r){
 //小范围数据使用插入排序
 if((r-l)<=THRESHOLD){
  inssort2(&b[l-1],r-l+1,1);
  return;
 }
 if(l==r) return ;
 int i,j,k,mid = (l+r)>>1;
 MergeSort2(b,tmp,l,mid);
 MergeSort2(b,tmp,mid+1,r);
 for(i=mid;i>=l;i--){
  tmp[i]=b[i];
 }
 for(j=1;j<=r-mid;j++){
  tmp[r-j+1]=b[j+mid];
 }
 for(i=l,j=r,k=l;k<=r;k++){
  if(tmp[i]>tmp[j]){
   b[k]=tmp[j--];
  }
  else b[k]=tmp[i++];
 }
} 

快排
写了半天还是错的 我晕了
下面的是错的 先记录一下5555555

//QuickSort
int findpivot(int b[],int l,int r){
 return b[(l+r)>>1];
}
int partition(int b[],int l,int r,int pivotval){
 do{
  while(l<r && b[++l]<pivotval);
  while(l<r && b[--r]>pivotval);
  swap(b[l],b[r]);
 }
 while(l<r);
 return l;
}
void QuickSort(int b[],int l,int r){
 if(r<=l) return ;
 int pivot=findpivot(b,l,r);
 swap(b[pivot],b[r]);
 int k=partition(b,l,r,b[r]);
 swap(b[k],b[r]);
 QuickSort(b,l,k-1);
 QuickSort(b,k+1,r);
}

三、外排序
好像敲不出板子
略过先

插播一个玄妙的字符串哈希函数

//M为哈希表容量
const int M = 101;
int cal(char * key){
 unsigned int *lkey = (unsigned int *)key; 
 int intlength=strlen(key)/4;
 unsigned int sum=0;
 for(int i=0;i<intlength;i++){
  sum+=lkey[i];
 }
 int ex = strlen(key)-4*intlength;
 char tmp[4];
 lkey=(unsigned int *)tmp;
 lkey[0]=0;
 for(int i=0;i<ex;i++){
  tmp[i]=key[intlength*4+i];
 } 
 sum+=lkey[0];
 return sum%M;
}

四、课本上的图论算法
1.最短路算法
Dijkstra

 //Dijkstra算法 
 //不停的找出最小的边所对应的结点,用这个节点去缩短其他的边 
 int dis[101];
 for(int i=1;i<=n;i++){
  dis[i]= city[1][i];
 }
  book[1] =1;
  for(int i=1;i<=n-1;i++){//?
   int min = inf;
   int t;
   for(int j=1;j<=n;j++){
    if(book[j]==0&& dis[j]<min){
     min = dis[j];
     t = j;//存储最小路径序号,因为j会变化 
    }
   }
   
   book[t] =1;
   for(int v = 1;v<=n;v++){
    if(city[t][v]!= inf){
     if(dis[v]>dis[t]+city[t][v])
     {
      dis[v] = dis[t]+city[t][v];
     }
    }
   } 
  }
cout<<dis[n];
 }

这是中缀表达式-》后缀表达式
相关操作
包括创建后缀表达式(栈+递归)
求解后缀表达式(栈+递归)
感谢英俊潇洒的队友提供的参考 orz

//JadeQ
//杭电 AC 代码 2020.4
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <map>
#include <stack>
#include <set>
#include <cmath>
#include <vector>
#include <queue>
#include <string>
#include <bitset>
#include <unordered_map>
#include <unordered_set>
#include<iomanip>
#define INF 0x3f3f3f3f
#define sz sizeof
#define mk make_pair
#define ll long long 
using namespace std;
const int maxn = 1e3 + 5;
double ans = 0;
int cnt = 0;  
char s[maxn];
string ss, tmp[maxn];
stack <char> in;
stack<string> out;
void pre(char *s) {
 int len = 0;
 for (int i = 0;i < strlen(s);i++) {
  if (s[i] != ' ') {
   ss += s[i];
  }
 }
}
bool isop(char x) {
 return x == '+' || x == '-' || x == '*' || x == '/';
}
bool isop(string x) {
 return isop(x[0]);
}
double deal(string x) {
 double res = 0;
 for (int i = 0;i < x.length();i++) {
  res *= 10;
  res += x[i] - '0';
 }
 return res;
}
double cal(double a, double b, char op) {
 if (op == '+') return double(b + a);
 if (op == '-') return double(b - a);
 if (op == '*') return double(b * a);
 if (op == '/') return double(b / (1.00* a));
}
string tostring(char x) {
 string tmp = "";
 tmp += x;
 return tmp;
}
bool bigger(char a, char b) {
 if ((b == '+' || b == '-') && (a == '*' || a == '/')) return 1;
 return 0;
}
int t = 0;
//逆波兰式的递归求解
double cal() {
 if (isop(tmp[++t])) {
  if (tmp[t] == "+") return cal() + cal();
  if (tmp[t] == "-") { 
   double t1 = cal(), t2 = cal();
   return t2-t1;
  }
  if (tmp[t] == "*") return cal()*cal();
  if (tmp[t] == "/") {
   double t1 = cal(), t2 = cal();
   return t2 / t1;
  }
 }
 else return deal(tmp[t]);
}
//用栈模拟计算ans
double use_stack() {
 stack<double> num;
 for (int i = cnt;i >= 1;i--) {
  //cout << tmp[i];
  if (!isop(tmp[i]))  num.push(deal(tmp[i]));
  else {
   double a = num.top();num.pop();
   double b = num.top();num.pop();
   num.push(cal(a, b, tmp[i][0]));
  }
 }
 return num.top();
}
int main() {
 ios::sync_with_stdio(false); cin.tie(0);
 //生成逆波兰式 -- (栈模拟,递归方法详见另一个cpp文件以及代码解释)
 while (cin.getline(s, maxn) && strlen(s) >= 1) {
  while (!in.empty()) in.pop();
  while (!out.empty()) out.pop();
  if (s[0] == '0' && strlen(s) == 1) break;
  ss = ""; ans = 0; pre(s); cnt = 0;t = 0;
  int last = 0;
  for (int i = 0;i < ss.length();i++) {
   if (isop(ss[i])) {
    out.push(ss.substr(last, i - last));
    last = i + 1;
    if (!in.empty() && !bigger(ss[i], in.top())) {
     while (!in.empty() && !bigger(ss[i], in.top())) {
      out.push(tostring(in.top()));
      in.pop();
     }
    }
    in.push(ss[i]);
   }
   else if (i == ss.length() - 1)
    out.push(ss.substr(last, i - last + 1));
  }
  while (!in.empty()) {
   out.push(tostring(in.top()));in.pop();
  }
  while (!out.empty()) {
   tmp[++cnt] = out.top();
   out.pop();
  }
  //(1)
  //用栈模拟计算ans
  //ans = use_stack();
  //(2)
  ans = cal();
  //也可以用递归求解逆波兰式
  //cout << endl;
  printf("%.2lf\n", ans);
 }
 return 0;
}
//qqq
//#include "pch.h"
//JadeQ  2020.4
//直接输出后缀表达式的程序
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <map>
#include <stack>
#include <set>
#include <cmath>
#include <vector>
#include <queue>
#include <string>
#include <bitset>
#include <unordered_map>
#include <unordered_set>
#include<iomanip>
#define INF 0x3f3f3f3f
#define sz sizeof
#define mk make_pair
#define ll long long 
using namespace std;
const int maxn = 1e3 + 5;
double ans = 0;
int cnt = 0,t = 0;
char s[maxn];
string tmp[maxn];
map<char, int> mp;
//预处理 去空格
string pre(char *s) {
 string ss = "";
 int len = 0;
 for (int i = 0;i < strlen(s);i++) {
  if (s[i] != ' ') {
   ss += s[i];
  }
 }
 return ss;
}
bool isop(char x) {
 return x == '+' || x == '-' || x == '*' || x == '/';
}
bool isop(string x) {
 return isop(x[0]);
}
bool isnum(string x) {
 for (int i = 0;i < x.length();i++)
  if (!(x[i] >= '0' && x[i] <= '9')) return 0;
 return 1;
}
//string->double
double deal(string x) {
 double res = 0;
 for (int i = 0;i < x.length();i++) {
  res *= 10;
  res += x[i] - '0';
 }
 return res;
}
//递归求解后缀表达式
double cal() {
 if (isop(tmp[++t])) {
  if (tmp[t] == "+") return cal() + cal();
  if (tmp[t] == "-") {
   double t1 = cal(), t2 = cal();
   return t2 - t1;
  }
  if (tmp[t] == "*") return cal()*cal();
  if (tmp[t] == "/") {
   double t1 = cal(), t2 = cal();
   return t2 / t1;
  }
 }
 else return deal(tmp[t]);
}
//生成逆波兰表达式--递归
string solve(string ss, int L, int R) {
 if (R >= L && isnum(ss.substr(L, R - L + 1)))  return ss.substr(L, R - L + 1);
 int pos = 0; char cur_op = ' ';
 for (int i = L;i <= R;i++) {
  if (isop(ss[i]) && mp[cur_op] >= mp[ss[i]]) {
   cur_op = ss[i];pos = i;
  }
 }
 return solve(ss, L, pos - 1) + " " + solve(ss, pos + 1, R) + " " + cur_op;
}
//复制生成的逆波兰表达式到目标字符串数组tmp(求解用)
void copy(string tmp[],string ttmp) {
 int st = 0, ed = 0;
 for (int i = 0;i < ttmp.length();i++) {
  if (i == ttmp.length() - 1) {
   ed = i;
   tmp[++cnt] = ttmp.substr(st, ed - st + 1);
  }
  if (ttmp[i] == ' ') {
   ed = i - 1;
   tmp[++cnt] = ttmp.substr(st, ed - st + 1);
   st = i + 1;
  }
 }
 for (int i = 1;i <= cnt / 2;i++) swap(tmp[i], tmp[cnt - i + 1]);
}
void ini() {
 ans = 0; 
 mp[' '] = 3;mp['+'] = mp['-'] = 1; mp['*'] = mp['/'] = 2;
 cnt = 0;t = 0;
}
int main() {
 ios::sync_with_stdio(false);cin.tie(0);
 while (cin.getline(s, maxn) && strlen(s) >= 1) {
  if (s[0] == '0' && strlen(s) == 1) break;
  ini();
  string ss = pre(s); 
  copy(tmp, solve(ss, 0, ss.length() - 1));
  ans = cal();
  printf("%.2lf\n", ans);
  
  //若要查看生成的逆波兰表达式,去钓掉下行注释
  //cout << solve(ss,0,ss.length()-1) << endl; 
 }
 return 0;
}
//#include"pch.h"
//JadeQ
//增加处理括号的情况 2020.4
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <map>
#include <stack>
#include <set>
#include <cmath>
#include <vector>
#include <queue>
#include <string>
#include <bitset>
#include <unordered_map>
#include <unordered_set>
#include<iomanip>
#define INF 0x3f3f3f3f
#define sz sizeof
#define mk make_pair
#define ll long long 
using namespace std;
const int maxn = 1e3 + 5;
int cnt = 0;
char s[maxn];
string ss, tmp[maxn];
stack <char> in;
stack<string> out;
void pre(char *s) {
 for (int i = 0;i < strlen(s);i++)
  if (s[i] != ' ') ss += s[i];
}
bool isop(char x) {return x == '+' || x == '-' || x == '*' || x == '/';}
bool isnum(char x) {return x >= '0'&&x <= '9';}
bool bigger(char a, char b) {
 if ((b == '+' || b == '-') && (a == '*' || a == '/')) return 1;
 return 0;
}
int main() {
 ios::sync_with_stdio(false); cin.tie(0);
 //生成逆波兰式 -- (栈模拟,递归方法详见另一个cpp文件以及代码解释)
 while (cin.getline(s, maxn) && strlen(s) >= 1) {
  while (!in.empty()) in.pop();
  while (!out.empty()) out.pop();
  if (s[0] == '0' && strlen(s) == 1) break;
  ss = "";  pre(s); cnt = 0;
  for (int i = 0;i < ss.length();i++) {
   if (isnum(ss[i])) {
    int ed = i, st = i;
    while (isnum(ss[ed])) ed++;
    out.push(ss.substr(st, ed - st));
    i = ed - 1;
   }
   else if (ss[i] == '(') in.push(ss[i]);
   else if (isop(ss[i])) {
    while (!in.empty() && !bigger(ss[i], in.top())&& in.top()!='(') {
      out.push(""+in.top());
      in.pop();
    }
    in.push(ss[i]);
   }
   else if (ss[i] == ')') {
    while (in.top() != '(') {
     out.push(""+in.top());in.pop();
    }
    in.pop();
   }
  }
  while (!in.empty()) {
   out.push(""+in.top());
   in.pop();
  }
  while (!out.empty()) {
   tmp[++cnt] = out.top();
   out.pop();
  }
  for (int i = cnt;i >= 1;i--) {
   cout << tmp[i]<<" ";
  }
  cout << endl;
 }
 return 0;
}

真正的码其实很少,都是乱七八糟的处理过程
完事了,舒服

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值