3上海交大ACM班C++算法与数据结构——数据结构之线性表

上海交大ACM班C++算法与数据结构——数据结构之线性表

3上海交大ACM班C++算法与数据结构——数据结构之线性表_第1张图片

1.线性表定义

  • 虚构类
    里面全部是虚构函数,以此为基类的类必须全部实现规定的功能才能实例化,以此来规定相应的类的功能。
    无需构造函数,因为可以在每个类的构造函数中完成。
  • 虚析构函数
    即使虚构类里面没有数据也要加上虚析构函数,这样当指针指向基类时候发现析构函数是虚的,就会去找子类的析构函数,防止内存泄漏。虚析构函数是为了解决父类指针指向子类对象时,释放子类对象的资源时,释放不完全,造成的内存泄漏问题。
  • 基本操作
    - 建空表
    - 清空
    - 求表长
    - 插入
    - 删除
    - 检索
    - 访问
    - 遍历
  • 虚构类实现
		template <class elemType>//由于不知道实际的元素是什么类型,所以将其作为类模板参数
		class list {
		  public: 
			 // 清除
			 virtual void clear() = 0;
			 // 长度
			 virtual int length() const = 0;
			 // 插入
			 virtual void insert(int i, const elemType &x) = 0; 
			 // 删除
			 virtual void remove(int i) = 0;
			 // 搜索
			 virtual int search(const elemType &x) const = 0;
			 // 访问
			 virtual elemType visit(int i) const = 0;
			 // 遍历
			 virtual void traverse() const = 0;
			 // 析构
			 virtual ~list() {};
		};

2.顺序表

  • 一个数组
  • 定位访问性能好,插入删除性能差,适合静态的、经常做线性访问的线性表
  • 类定义实现
		template <class elemType>
		class seqList: public list<elemType> { 
		private:
			elemType *data;
			int currentLength;
			int maxSize;
			// 为了解决插入元素时,数组容量不够的问题
			void doubleSpace();
		public:
			seqList( int initSize = 10 );
			// 析构函数:还掉动态空间
			~seqList( )  {  delete [] data;  }
			// 清空数组元素:只要把表长设为0
			void clear( )  {   currentLength = 0;   }
			// 数组长度:只需要返回这个私有成员变量currentLength
			int length( ) const  {   return currentLength;   }//此处const表示这个函数不会对数组的值进行修改
			void insert( int i, const elemType &x); //此处const表示这个函数对x进行修改
			void remove( int i );  
			int search( const elemType &x ) const ;
			// 访问数组元素:返回下标为i的数组元素
			elemType visit( int i) const { return data[i]  ; }
			void traverse( ) const ;
		};
  • 建空表(构造函数)
		template <class elemType>
		seqList<elemType>::seqList(int initSize) {
			data = new elemType[initSize]; 
			maxSize = initSize; 
			currentLength = 0;
		} 
  • 检索
		template <class elemType>
		int seqList<elemType>::search (const elemType &x) const {
			int i;
			// 空循环,不满足循环条件时跳出
			for (i = 0; i < currentLength && data[i] != x; ++i) ;
			if (i == currentLength)
				return -1; 
			else return i;
		}
  • 遍历
		template <class elemType>
		void seqList<elemType>::traverse() const {
			cout << endl;
			for (int i = 0; i < currentLength; ++i) 
				cout << data[i] << ' ';
		}
  • 插入
		template <class elemType>
		void seqList<elemType>::insert(int i, const elemType &x) {
			// 如果数组放满了,先扩大数组空间
			if (currentLength == maxSize) 
				doubleSpace(); 
			// 从后往前,元素后移
			for ( int j = currentLength; j > i; j--) 
				data[j] = data[j-1];
			// 空出空间,插入元素,表长加1
			data[i] = x;
			++currentLength;
		}
  • 扩容(doubleSpace函数,私有函数,因为这个功能不需要让用户知道)
		template <class elemType>
		void seqList<elemType>::doubleSpace() { 
			//实现功能:将容量扩大两倍,并保留有原本的数据
			// 保存指向原来空间的指针
			elemType *tmp = data;
			// 重新申请空间
			maxSize *= 2;
			data = new elemType[maxSize];
			// 拷贝原有数据
			for (int i = 0; i < currentLength; ++i) 
				data[i] = tmp[i];
			// 清除原来申请的空间
			delete [] tmp;
		} 
  • 删除
		template <class elemType>
		void seqList<elemType>::remove(int i) {
			// 后面所有元素前移,表长减1
			for (int j = i; j < currentLength - 1; j++) 
				data[j] = data[j+1] ;
			--currentLength;
		} 

3.单链表

  • 需要定义一个结点结构体
  • 适合插入、删除,无法进行定位访问,适合不常定位访问的动态表
  • 要注意及时释放内存,关注内存泄漏问题
  • 类定义实现
		template <class elemType>
		class sLinkList: public list<elemType> { 
			private:
				// 定义一个结点的结构体,里面的所有成员都是公有的
				//虽然成员是公有的,但是node在单链表类的私有成员里,所以用户根本看不到,而链表的操作可以看到单链表类的私有成员,可以操作node的公有成员
				//class定义,没有声明的情况下成员都为私有;
				//struct定义,没有声明的情况下成员都为公有
				struct node {                                  
					elemType data;
					node *next;
					node(const elemType &x, node *n = NULL) { data = x; next = n; }
					node( ):next(NULL) { }//缺省情况下的构造函数
					~node() {};
				};

				node  *head;                                         
				int currentLength;//保存链表长度,否则想要获取长度需要遍历整个链表                               
				node *move(int i) const; //操作过程中常常要移动指针指向第i个元素,所以将其设计为一个私有的辅助的工具函数                 

			public:
				// 构造函数:创建空的单链表
				sLinkList() { 
					head = new node;
					currentLength = 0;
				}
				// 析构函数:调用clear把单链表的所有结点都还掉,再把头结点还掉
				~sLinkList() { clear(); delete head; } 
				void clear() ;
				// 表的长度:返回私有的成员变量
				int length() const { return currentLength; }
				void insert(int i, const elemType &x); 
				void remove(int i);  
				int search(const elemType &x) const  ;
				elemType visit(int i) const;
				void traverse() const ;  
		}; 
  • clear():把单链表变成一个空表。注意需要把所有结点的空间还给系统。
		template <class elemType>
		void sLinkList<elemType>::clear() { 
			node *p = head->next, *q;

			head->next = NULL;
			// 只要p不是空的,把p的下一个结点记下来,删掉p,再把q赋给p
			while (p != NULL) {     
				q = p->next;
				delete p;
				p=q;
			}
			currentLength = 0;
		}           
  • insert(i,x):在第i个位置插入元素x。
    先让指针指向第i-1个元素,之后执行插入过程,即申请一个存放x的结点,让该结点的后继指针指向结点i,让第i-1个结点的后继指针指向这个新结点。
		template <class elemType>
		void sLinkList<elemType>::insert(int i, const elemType &x) {
			node *pos;

			// 通过move函数找到第i-1个元素的地址
			pos = move(i - 1);
			pos->next = new node(x, pos->next);
			++currentLength;
		}
  • move(i): 返回指向第i个元素的指针。
		template <class elemType>
		sLinkList<elemType>::node * sLinkList<elemType>::move(int i) const {
			node *p = head;  
			while (i-- >= 0) p = p->next;
			return p;
		}
  • remove(i):删除第i个位置的元素。
    先找到第i-1个结点,让该结点的后继指针指向第i+1个结点,释放被删结点空间。
		template <class elemType>
		void sLinkList<elemType>::remove(int i) {
			node *pos, *delp;

			// 通过move函数找到第i-1个元素的地址
			pos = move(i - 1);
			// 找到被删结点的位置,让第i-1个元素的后继指针指向第i+1个结点
			delp = pos->next;
			pos->next = delp->next;
			// 释放被删结点的空间
			delete delp;
			--currentLength;
		}
  • search(x):搜索某个元素在线性表中是否出现。从头指针的后继结点开始往后检查链表的结点直到找到x或查找到表尾。
		template <class elemType>
		int sLinkList<elemType>::search(const elemType &x) const {
			// 指针指向第一个元素A_0
			node *p = head->next;

			int i = 0;
			while (p != NULL && p->data != x) {
				p = p->next; 
				++i;
			}
			// p为NULL表示找到表尾都没有找到
			if (p == NULL)
				return -1;
			else 
				return i;
		}
  • visit(i):访问线性表的第i个元素。通过move(i)找到第i个结点,返回该结点的数据部分
		template <class elemType>
		elemType sLinkList<elemType>::visit(int i) const {
			return move(i)->data;
		}
  • traverse():遍历运算。从头结点的直接后继开始重复:输出当前结点值,将后继结点设为当前结点,直到当前节点为空。
		template <class elemType>
		void sLinkList<elemType>::traverse() const {
			node *p = head->next;
			cout << endl;
			while (p != NULL) {
				cout << p->data << "  ";
				p=p->next;
			}
			cout << endl;
		}

4.双链表

  • 需要定义一个结点结构体
  • 相比单链表,定位前一个结点不需要从头开始找了
  • 数据成员:头指针、尾指针、链表长度
  • 要注意及时释放内存,关注内存泄漏问题
  • 类定义实现
		template <class elemType>
		class dLinkList: public list<elemType> {
			private:
				struct node {                                         
					elemType  data;
					node *prev, *next; 
					node(const elemType &x, node *p = NULL, node *n = NULL) { data = x; next = n; prev = p; }
					node( ):next(NULL), prev(NULL) {}
					~node() {}
				};

				node *head, *tail;                 
				int currentLength;  
				node *move(int i) const;

			public:
				dLinkList();
				// 析构函数:先调用clear删除所有元素,再删除头尾结点
				~dLinkList() {
					clear(); 
					delete head; 
					delete tail;
				}

				void clear();
				// 返回元素数量
				int length() const { return currentLength; }
				void insert(int i, const elemType &x); 
				void remove(int i);  
				int search(const elemType &x) const;
				elemType visit(int i) const;
				void traverse() const;  
		};
  • 构造函数:创建一个双链表就是创建一个只有头尾结点的链表,其中头结点的前驱为空,尾结点的后继为空。
		template <class elemType>
		dLinkList<elemType>::dLinkList() { 
			head = new node;
			head->next = tail = new node;
			tail->prev = head;
			currentLength = 0;
		}
  • 插入:让指针指向第i个元素,执行插入过程,具体方式基本与单链表一致。
		template <class elemType>
		void dLinkList<elemType>::insert(int i, const elemType &x) {
			node *pos, *tmp;

			pos = move(i);                  
			tmp = new node(x, pos->prev, pos);
			pos->prev->next = tmp;        
			pos->prev = tmp;            

			++currentLength;
		}
  • 删除:让指针指向第i个元素,执行删除过程,具体方式基本与单链表一致。
		template <class elemType>
		void dLinkList<elemType>::remove(int i) {
			node *pos;

			pos = move(i);                       
			pos->prev->next = pos->next;           
			pos->next->prev = pos->prev;

			delete pos;
			--currentLength;
		}
  • 其他操作:双链表其他操作和单链表基本相同。

  • 例题

    一个学校里老师要将班上N个同学排成一列,同学被编号为1∼N,他采取如下的方法:

    先将1号同学安排进队列,这时队列中只有他一个人;
    2−N号同学依次入列,编号为i的同学入列方式为:老师指定编号为i的同学站在编号为1 ~ (i-1)中某位同学(即之前已经入列的同学)的左边或右边;
    从队列中去掉M (M 在所有同学按照上述方法队列排列完毕后,老师想知道从左到右所有同学的编号。

    输入描述:

    第1行为一个正整数N,表示了有N个同学。

    第2−N行,第i行包含两个整数k p,其中k为小于i的正整数,p为0或者1。若p为0,则表示将i号同学插入到k号同学的左边,p为1则表示插入到右边。

    第N+1行为一个正整数M,表示去掉的同学数目。

    接下来M行,每行一个正整数x,表示将x号同学从队列中移去,如果x号同学已经不在队列中则忽略这一条指令。

    输出描述:

    一行,包含最多N个空格隔开的正整数,表示了队列从左到右所有同学的编号,行末换行且无空格。

    示例 1:
    输入:
    4
    1 0
    2 1
    1 0
    2
    3
    3
    输出:
    2 4 1

		#include  
		#include  
		#include  
		#include  
		#define N 100005 
		using namespace std; 

		struct node { 
			int l, r; 
		}nod[N]; 

		int head, tail; 
		void add(int a, int b, int c) { 
			nod[a].r = b; 
			nod[b].r = c; 
			nod[c].l = b; 
			nod[b].l = a; 
		} 

		void del(int a, int b, int c) { 
			nod[a].r = c; 
			nod[c].l = a; 
			nod[b].r = nod[b].l = 0; 
		} 

		int n, m; 
		int main() { 
			scanf("%d", &n); 
			head = n + 1;  
			tail = n + 2; 
			nod[head].r = tail; 
			nod[tail].l = head; 
			add(head, 1, tail); 

			for (int i = 2; i <= n; ++i) { 
				int x, y; 
				scanf("%d%d", &x, &y); 
				if (!y) add(nod[x].l, i, x); 
				else add(x, i, nod[x].r); 
			} 
			scanf("%d", &m); 
			for (int i = 0; i < m; ++i) { 
				int x; 
				scanf("%d", &x); 
				if (!nod[x].l) continue; 
				del(nod[x].l, x, nod[x].r); 
			} 

			for (int i = nod[head].r; i != tail; i = nod[i].r) printf("%d ", i); 
			return 0; 
		}
		```

 

你可能感兴趣的:(上海交大ACM班C,++算法与数据结构,c++,算法,数据结构,职场和发展,面试)