2012.09.10阿里笔试

投了个内推,在南大旧的就业中心技术沙龙和面试

9.10号晚笔试题

1、正则表达式,邮件合法性检测,给出正则表达式规则,让写正确的正则表达式

2、统计英文文章单词个数,并按出现顺序打印出来,自己设计数据结构和算法

1、解答: 以下是一个不区分大小写的正则表达式:/^[a-z]([a-z0-9]*[-_]?[a-z0-9]+)*@([a-z0-9]*[-_]?[a-z0-9]+)+[.][a-z]{2,3}([.][a-z]{2})?$/i;
2、解答:

步骤1:从头开始读取每一个字符,用char *c来引用,遇到一个空格或者标点符号,一个单词结束;

步骤2:输出单词该单词,对这个单词进行hash处理:hash(*c)%1000,存入一个hashmap中,对应的<key,value>是单词和单词出现的次数;

步骤3:读取第2个单词,hash取模后判断是否在hashmap中,如果在,则对应的value加1,不在则输出,并存入到hashmap中;

步骤4;重复上述三个步骤,直到读取到文章末尾。

另,9.6的笔试题

1、设计一个分布式消息系统

2、求无向图的环的个数

9.12面试

1、面试官自己介绍,再让我自我介绍 2、说说自认为学的最好的一门课(楼主选了数据结构,面试官简历上标记) 3、说一下所有数据结构的特点;栈和队列的差别,数组和链表的差别

解答:

①从数据的逻辑结构分,数据结构分为线性结构和非线性结构。线性结构又分为一般的线性表、受限线性表和线性表推广。其中受限线性表包括栈、队列和串。线性表推广包括数组和广义表。非线性表包括集合、树形结构和图状结构。其中树形结构分为一般树和二叉树,图分为有向图和无向图。

线性表:线性表是具有相同类型的n(n>=0)个数据元素的有限序列。

线性表有两种表示形式:顺序表和链表。顺序表是用一组地址连续的存储单元,依次存储线性表中数据元素;链表不要求逻辑上相邻的两个元素逻辑上也相邻,它是通过“链”建立起数据元素之间的逻辑关系。

串: 串(或字符串),是由零个或多个字符组成的有穷序列。

树:树是N(N>=0)个结点的有限集合,N=0时,称为空树。在任意一棵非空树中应满足:1)有且仅有一个特定的称为根的结点;2)当N>1时,其余结点可分为m(m>0)个互不相交的有限集合T1、T2,···,Tm,其中每一个集合本身又是一棵树,并且称为根结点的子树。显然树的定义是递归的,是一种递归的数据结构。

图:由顶点集以及边集合组成的数据结构。

②栈:只允许在一端进行插入和删除的线性表,后进先出;队列:在一端进行插入,另一端删除的线性表,先进先出。

③数组是用一组地址连续的存储单元,依次存储线性表中数据元素,通过下标就能迅速访问数组中的任意元素,但同时插入、删除就比较麻烦;链表访问元素需要遍历,时间复杂度高,插入删除只要操作操作指针就行。

4、给10分钟写一个队列的出队入队操作

 用C语言以及链式存储实现。

Queue.h文件:

//第一种结构定义方法
//typedef struct node  
//{  
//    int data;  
//    struct node *next;  
//}Node,*PNode; 

//第二章结构定义方法
typedef struct node *PNode;
typedef struct node  
{  
    int data;  
    PNode next;
}Node;

typedef struct{
	PNode front;
	PNode rear;
	int size;
}Queue;

//构造一个空队列
Queue *InitQueue();

//销毁一个队列
void DestroyQueue(Queue *pqueue);

//清空一个队列
void ClearQueue(Queue *pqueue);

//判断队列是否为空
int IsEmpty(Queue *pqueue);

//返回队列大小
int GetSize(Queue *pqueue);

//返回队头元素
PNode GETFront(Queue *pqueue,int *item);

//返回队尾元素
PNode GETRear(Queue *pqueue,int *item);

//将新元素入队
PNode EnQueue(Queue *pqueue,int item);

//将新元素出队
PNode DeQueue(Queue *pqueue,int *item);

 Queue.c文件:

#include "Queue.h"
#include <malloc.h>
#include <stdio.h>

//构造一个空队列
Queue *InitQueue(){
	Queue *queue = (Queue *)malloc(sizeof(Queue));
	if(queue != NULL){
		queue->front = NULL;
		queue->rear = NULL;
		queue->size = 0;
	}

	return queue;
}

//判断队列是否为空
int IsEmpty(Queue *pqueue){
	if(pqueue->front == NULL && pqueue->rear == NULL)
		return 1;
	else 
		return 0;
}


//返回队列大小
int GetSize(Queue *pqueue){
	return pqueue->size;
}

//返回队头元素
PNode GETFront(Queue *pqueue,int *item){
	if(IsEmpty(pqueue)!=1&&item!=NULL)  
    {  
       *item = pqueue->front->data;  
    }  
    return pqueue->front;  
}

//返回队尾元素
PNode GETRear(Queue *pqueue,int *item){
	if(IsEmpty(pqueue)!=1&&item!=NULL)  
    {  
       *item = pqueue->rear->data;  
    }  
    return pqueue->rear;   
}

//将新元素入队
PNode EnQueue(Queue *pqueue,int item){
	PNode node = (PNode)malloc(sizeof(item));
	if(node != NULL){
		node->data = item;
		node->next = NULL;
		if(IsEmpty(pqueue)){
			//如果队列为空
			pqueue->front = node;
		}else{
			pqueue->rear->next = node;
		}
		pqueue->rear = node;
		pqueue->size++;
	}
	return node;
}

//将新元素出队
PNode DeQueue(Queue *pqueue,int *item){
	PNode node = pqueue->front;
	if(IsEmpty(pqueue)!=1 && node != NULL){
		pqueue->front = node->next;
		free(node);
		pqueue->size--;
		if(pqueue->size == 0)
			pqueue->rear = NULL;

	}
	return pqueue->front;
}

/**测试**/
void print(int i)
{
	printf("该节点元素为%d
",i);
}
main()
{
	Queue *pq = InitQueue();
	int i,item;
	printf("0-9依次入队并输出如下:
");
	for(i=0;i<10;i++)
	{
		EnQueue(pq,i);
		GETRear(pq,&item);
		printf("%d ",item);
	}

	
}

  

5、多线程死锁写个示例

多线程死锁的四个必要条件:

4.1、互斥使用(资源独占)
一个资源每次只能给一个进程使用
4.2、不可强占(不可剥夺)
    资源申请者不能强行的从资源占有者手中夺取资源,资源只能由占有者自愿释放
4.3、请求和保持(部分分配,占有申请)
一个进程在申请新的资源的同时保持对原有资源的占有(只有这样才是动态申请,动态分配)
4.4、循环等待
存在一个进程等待队列
{P1 , P2 , … , Pn},
其中P1等待P2占有的资源,P2等待P3占有的资源,…,Pn等待P1占有的资源,形成一个进程等待环路

实际例子:

package com.damlab.fz;

public class DeadLock {
    public static void main(String[] args) {
        Resource r1= new Resource();
        Resource r2= new Resource();
        //每个线程都拥有r1,r2两个对象
        Thread myTh1 = new MyThread1(r1,r2);
        Thread myTh2 = new MyThread2(r1,r2);
        myTh1.start();
        myTh2.start();
    }
}

class Resource{
    private int i;
}

class MyThread1 extends Thread{
    private Resource r1,r2;
    public MyThread1(Resource r1, Resource r2) {
        this.r1 = r1;
        this.r2 = r2;
    }

    @Override
    public void run() {
        while(true){
        //先获得r1的锁,再获得r2的锁    
        synchronized (r1) {
            System.out.println("1号线程获取了r1的锁");
            synchronized (r2) {
                System.out.println("1号线程获取了r2的锁");
            }
        }
        }
    }
    
}

class MyThread2 extends Thread{
    private Resource r1,r2;
    public MyThread2(Resource r1, Resource r2) {
        this.r1 = r1;
        this.r2 = r2;
    }

    @Override
    public void run() {
        while(true){
        //先获得r2的锁,再获得r1的锁
        synchronized (r2) {
            System.out.println("2号线程获取了r2的锁");
            synchronized (r1) {
                System.out.println("2号线程获取了r1的锁");
            }
        }
        }
    }
    
}

6、TCPIP有哪几种状态

解答:其实就是TCP通过三次握手进行连接和通过四次握手释放连接的过程中,TCP有几种状态。

详见http://blog.sina.com.cn/s/blog_6a2787d40102uwte.html

7、词法分析和语法分析作用是什么

解答:编译过程包括词法分析,语法分析,语义检查,代码生成,代码优化。

词法分析:分析由字符组成的单词是否合法,如果没有问题的话,则产生一个单词流。

语法分析:分析由单词组成的句子是否合法,如果没有问题的话,则产生一个语法树。

8、一亿个数存在一个大文件里,现在给出一个数,怎么判断这数是否在文件里

解答:遍历这一亿个数,用bit-map存储这一亿个数,对应的bit位置为1,只需要约10^8/8byte=12.5Mb的内存存储空间。根据给出的数,通过位运算查找该数对应的位置上是否为1,如果是则表明该数在文件里,否则不在文件中。

9、最有成就感的项目

10、对比java和c++;c++的内存管理是怎么做的

解答:
1.
Java中对内存的分配是动态的,它采用面向对象的机制,采用运算符new为每个对象分配内存空间,而且,实际内存还会随程序运行情况而改变.程序运行中,每个, Java系统自动对内存进行扫描,对长期不用的空间作为”垃圾”进行收集,使得系统资源得到更充分地利用.按照这种机制,程序员不必关注内存管理问题,这使Java程序的编写变得简单明了,并且避免了了由于内存管理方面的差错而导致系统出问题.而C语言通过malloc()和free()这两个库函数来分别实现分配内在和释放内存空间的,C++语言中则通过运算符new和delete来分配和释放内存.在C和C++这仲机制中,程序员必须非常仔细地处理内存的使用问题.一方面,如果对己释放的内存再作释放或者对未曾分配的内存作释放,都会造成死机;而另一方面,如果对长期不用的或不再使用的内存不释放,则会浪费系统资源,甚至因此造成资源枯竭.
  • Java不在所有类之外定义全局变量,而是在某个类中定义一种公用静态的变量来完成全局变量的功能.
  • Java不用goto语句,而是用try-catch-finally异常处理语句来代替goto语句处理出错的功能.
  • Java不支持头文件,面C和C++语言中都用头文件来定义类的原型,全局变量,库函数等,这种采用头文件的结构使得系统的运行维护相当繁杂.
  • Java不支持宏定义,而是使用关键字final来定义常量,在C++中则采用宏定义来实现常量定义,这不得于程序的可读性.
  • Java对每种数据类型都分配固定长度.比如,在Java中,int类型总是32位的,而在C和C++中,对于不同的平台,同一个数据类型分配不同的字节数,同样是int类型,在PC机中为二字节即16位,而在VAX-11中,则为32位.这使得C语言造成不可移植性,而Java则具有跨平台性(平台无关性).
  • 类型转换不同.在C和C++中,可通过指针进行任意的类型转换,常常带来不安全性,而在Java中,运行时系统对对象的处理要进行类型相容性检查,以防止不安全的转换.
  • 结构和联合的处理.在C和C++中,结构和联合的所有成员均为公有,这就带来了安全性问题,而在Java中根本就不包含结构和联合,所有的内容都封装在类里面
  • Java不再使用指针.指针是C和C++中最灵活,也最容易产生错误的数据类型.由指针所进行的内存地址操作常会造成不可预知的错误,同时通过指针对某个内存地址进行显式类型转换后,可以访问一个C++中的私有成员,从而破坏安全性.而Java对指针进行完全地控制,程序员不能直接进行任何指针操作.
     

C++内存管理:详见http://www.cnblogs.com/lancidie/archive/2011/08/05/2128318.html

原文地址:https://www.cnblogs.com/hzhtracy/p/4454317.html